@gloom-sh/terminal 0.19.2 → 0.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +108 -1
- package/src/account/upgrade-dialog.tsx +4 -23
- package/src/api-client/coalesce.ts +118 -0
- package/src/api-client/credit-boards.ts +13 -0
- package/src/api-client/crypto-etf-flows.ts +6 -0
- package/src/api-client/crypto-markets.ts +40 -0
- package/src/api-client/data.ts +201 -14
- package/src/api-client/equity-screener.ts +10 -0
- package/src/api-client/errors.ts +20 -0
- package/src/api-client/gpu.ts +29 -0
- package/src/api-client/index.ts +5 -2
- package/src/api-client/plan-access.ts +10 -1
- package/src/api-client/plan-rules.ts +35 -0
- package/src/api-client/research-activity.ts +1 -32
- package/src/api-client/types.ts +21 -1
- package/src/app/attention-notice.tsx +94 -0
- package/src/app.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +2 -1
- package/src/capabilities/factories.ts +1 -0
- package/src/cli/ascii-output.ts +42 -0
- package/src/cli/base-converter.ts +10 -2
- package/src/cli/command-options.ts +16 -1
- package/src/cli/commands/crypto-hints.ts +23 -4
- package/src/cli/commands/market.ts +92 -23
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +9 -4
- package/src/cli/commands/search.ts +71 -12
- package/src/cli/commands/system.ts +22 -30
- package/src/cli/commands/ticker.ts +49 -9
- package/src/cli/commands/version.ts +33 -0
- package/src/cli/context.ts +5 -9
- package/src/cli/data-dir.ts +7 -0
- package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
- package/src/cli/desktop-pane-shot-browser.ts +320 -0
- package/src/cli/desktop-pane-shot-routes.ts +26 -1
- package/src/cli/desktop-pane-shot.ts +196 -256
- package/src/cli/early-command.ts +42 -0
- package/src/cli/earnings-calendar.ts +9 -0
- package/src/cli/entry.ts +19 -13
- package/src/cli/errors.ts +12 -2
- package/src/cli/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +154 -4
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +40 -5
- package/src/cli/listing-arg.ts +46 -9
- package/src/cli/options.ts +1 -1
- package/src/cli/output-file.ts +60 -13
- package/src/cli/pane-functions/catalog.ts +14 -3
- package/src/cli/pane-functions/data.ts +1 -1
- package/src/cli/pane-functions/dom.ts +45 -0
- package/src/cli/pane-functions/freshness.ts +46 -9
- package/src/cli/pane-functions/function-help.ts +17 -9
- package/src/cli/pane-functions/headless.ts +22 -14
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +6 -2
- package/src/cli/pane-functions/resolver.ts +27 -5
- package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
- package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
- package/src/cli/pane-functions/screenshot-http.ts +70 -0
- package/src/cli/pane-functions/screenshot-render.ts +19 -5
- package/src/cli/quote-answer.ts +20 -0
- package/src/cli/registry.ts +41 -13
- package/src/cli/result.ts +12 -6
- package/src/cli/ticker-resolution.ts +2 -1
- package/src/cli/types.ts +2 -0
- package/src/components/chart/composite/format.ts +3 -0
- package/src/components/chart-table/header.tsx +1 -1
- package/src/components/chart-table/layout.ts +28 -11
- package/src/components/command-bar/action-runtime.ts +4 -0
- package/src/components/command-bar/commands/direct/index.ts +18 -33
- package/src/components/command-bar/commands/direct/runtime.ts +5 -0
- package/src/components/command-bar/commands/registry.ts +10 -1
- package/src/components/command-bar/routes/root/command-items.ts +9 -4
- package/src/components/command-bar/routes/root/runtime.ts +24 -1
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/header.tsx +8 -3
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +7 -2
- package/src/components/layout/pane/footer/model.ts +40 -5
- package/src/components/market-board/index.tsx +3 -2
- package/src/components/onboarding/desks-step.tsx +12 -4
- package/src/components/onboarding/first-run-workspace.ts +81 -4
- package/src/components/onboarding/onboarding-frame.tsx +2 -1
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +54 -23
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
- package/src/components/onboarding/run-setup-again.ts +24 -0
- package/src/components/onboarding/use-onboarding-progress.ts +33 -15
- package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
- package/src/components/onboarding/wizard-model.ts +57 -3
- package/src/components/onboarding/wizard-positions.ts +1 -1
- package/src/components/ui/delayed-mark.tsx +56 -0
- package/src/components/ui/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +8 -1
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +34 -31
- package/src/data/sqlite/access.ts +19 -8
- package/src/data/sqlite/checkpointer.ts +95 -8
- package/src/data/sqlite/database.ts +40 -7
- package/src/docs/snapshot.json +191 -78
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -2
- package/src/layout/desk-keys.ts +4 -1
- package/src/layout/desks.ts +38 -5
- package/src/layout-marketplace/gallery-desktop.tsx +45 -16
- package/src/market-data/batched-reads.ts +16 -0
- package/src/market-data/coordinator/auxiliary.ts +4 -2
- package/src/market-data/coordinator/index.ts +11 -2
- package/src/market-data/futures-board.ts +1 -1
- package/src/market-data/futures-contract.ts +3 -1
- package/src/market-data/market/index-level.ts +6 -4
- package/src/market-data/published-apac-sessions.ts +6 -0
- package/src/news/aggregator.ts +7 -4
- package/src/news/news-model.ts +11 -0
- package/src/news/story-id.ts +22 -0
- package/src/news/ticker-symbols.ts +17 -1
- package/src/notifications/app-notifier.ts +35 -14
- package/src/plugins/builtin/account-management/form-components.tsx +1 -1
- package/src/plugins/builtin/account-management/index.tsx +2 -3
- package/src/plugins/builtin/account-management/model.ts +1 -1
- package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
- package/src/plugins/builtin/alerts/events.ts +10 -2
- package/src/plugins/builtin/alerts/index.tsx +5 -5
- package/src/plugins/builtin/alerts/research-builder.ts +5 -1
- package/src/plugins/builtin/alerts/research-rules.ts +18 -0
- package/src/plugins/builtin/alerts/targets.ts +4 -1
- package/src/plugins/builtin/attention/headless.ts +1 -1
- package/src/plugins/builtin/attention/index.tsx +2 -4
- package/src/plugins/builtin/attention/listing.ts +1 -1
- package/src/plugins/builtin/attention/series.ts +1 -1
- package/src/plugins/builtin/awards/index.tsx +1 -2
- package/src/plugins/builtin/backtest/client.ts +1 -0
- package/src/plugins/builtin/backtest/index.tsx +1 -2
- package/src/plugins/builtin/backtest/rules.ts +3 -1
- package/src/plugins/builtin/backtest/window.ts +8 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
- package/src/plugins/builtin/broker-manager/index.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +5 -0
- package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
- package/src/plugins/builtin/buildout/company/id.ts +2 -0
- package/src/plugins/builtin/buildout/company/model.ts +247 -0
- package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
- package/src/plugins/builtin/buildout/facilities.ts +309 -0
- package/src/plugins/builtin/buildout/format.ts +9 -0
- package/src/plugins/builtin/buildout/index.tsx +51 -5
- package/src/plugins/builtin/buildout/model/index.ts +62 -10
- package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
- package/src/plugins/builtin/buildout/model/types.ts +11 -2
- package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
- package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
- package/src/plugins/builtin/buildout/table-model.ts +26 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
- package/src/plugins/builtin/cds/headless.ts +10 -2
- package/src/plugins/builtin/cds/model.ts +7 -3
- package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
- package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
- package/src/plugins/builtin/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -2
- package/src/plugins/builtin/chart-composer/headless.ts +88 -21
- package/src/plugins/builtin/chart-composer/index.tsx +2 -3
- package/src/plugins/builtin/chart-composer/pane.tsx +26 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/controller/index.ts +1 -1
- package/src/plugins/builtin/chat/controller/messages.ts +15 -4
- package/src/plugins/builtin/chat/ghost-user.ts +8 -0
- package/src/plugins/builtin/chat/index.tsx +5 -5
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/index.tsx +1 -2
- package/src/plugins/builtin/cloud/plugin.tsx +6 -4
- package/src/plugins/builtin/cloud/team/module.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
- package/src/plugins/builtin/comment-letters/index.tsx +1 -2
- package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
- package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
- package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
- package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
- package/src/plugins/builtin/congress-trades/headless.ts +2 -0
- package/src/plugins/builtin/congress-trades/model.ts +92 -64
- package/src/plugins/builtin/congress-trades/table.tsx +14 -2
- package/src/plugins/builtin/connections/index.ts +2 -3
- package/src/plugins/builtin/cot/index.tsx +1 -2
- package/src/plugins/builtin/cpi/index.tsx +1 -2
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/client.ts +7 -2
- package/src/plugins/builtin/credit-boards/headless.ts +25 -12
- package/src/plugins/builtin/credit-boards/model.ts +29 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- package/src/plugins/builtin/crypto-board/headless.ts +4 -3
- package/src/plugins/builtin/crypto-board/index.tsx +4 -2
- package/src/plugins/builtin/crypto-board/series.ts +2 -0
- package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
- package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
- package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
- package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
- package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
- package/src/plugins/builtin/debt-maturities/model.ts +17 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
- package/src/plugins/builtin/debug/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/client.ts +32 -2
- package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
- package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
- package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
- package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
- package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
- package/src/plugins/builtin/dividend-yield/view.ts +21 -0
- package/src/plugins/builtin/docs/index.tsx +2 -3
- package/src/plugins/builtin/doe/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
- package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
- package/src/plugins/builtin/econ/calendar-model.ts +29 -6
- package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +1 -0
- package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
- package/src/plugins/builtin/equity-screener/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/model.ts +28 -4
- package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
- package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
- package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
- package/src/plugins/builtin/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/cli.ts +44 -0
- package/src/plugins/builtin/fear-greed/index.tsx +5 -53
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
- package/src/plugins/builtin/fixed-income/index.tsx +1 -2
- package/src/plugins/builtin/futures/board-reads.ts +25 -0
- package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
- package/src/plugins/builtin/futures/index.tsx +15 -0
- package/src/plugins/builtin/futures/model.ts +14 -0
- package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
- package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
- package/src/plugins/builtin/futures-curve/headless.ts +6 -1
- package/src/plugins/builtin/futures-curve/index.tsx +1 -2
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +51 -12
- package/src/plugins/builtin/gpu/client.ts +15 -2
- package/src/plugins/builtin/gpu/equities.tsx +5 -2
- package/src/plugins/builtin/gpu/headless.ts +14 -2
- package/src/plugins/builtin/gpu/index.tsx +1 -2
- package/src/plugins/builtin/gpu/model.ts +20 -2
- package/src/plugins/builtin/gpu/pane.tsx +1 -1
- package/src/plugins/builtin/help/index.tsx +1 -1
- package/src/plugins/builtin/holders/index-holders.ts +24 -12
- package/src/plugins/builtin/holders/index.tsx +2 -3
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
- package/src/plugins/builtin/iv-history/index.tsx +1 -2
- package/src/plugins/builtin/jodi/index.tsx +1 -2
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
- package/src/plugins/builtin/layout-manager/index.tsx +9 -8
- package/src/plugins/builtin/macro-day/pane.tsx +4 -2
- package/src/plugins/builtin/market-halts/index.tsx +1 -2
- package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
- package/src/plugins/builtin/market-movers/index.tsx +7 -1
- package/src/plugins/builtin/market-movers/session-body.tsx +4 -1
- package/src/plugins/builtin/market-valuation/index.tsx +1 -2
- package/src/plugins/builtin/members/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/pane.tsx +2 -1
- package/src/plugins/builtin/news/headless.ts +7 -0
- package/src/plugins/builtin/news/wire/index.tsx +1 -1
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
- package/src/plugins/builtin/notes/index.tsx +3 -6
- package/src/plugins/builtin/openfda/index.tsx +1 -2
- package/src/plugins/builtin/options/view.tsx +6 -5
- package/src/plugins/builtin/options-calculator/index.tsx +1 -2
- package/src/plugins/builtin/options-positioning/index.tsx +1 -2
- package/src/plugins/builtin/options-scenario/index.tsx +2 -4
- package/src/plugins/builtin/perps/index.tsx +1 -2
- package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
- package/src/plugins/builtin/plugin-module.ts +16 -2
- package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
- package/src/plugins/builtin/power/index.tsx +1 -2
- package/src/plugins/builtin/rate-path/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/model.ts +16 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
- package/src/plugins/builtin/research/analyst-headless.ts +33 -6
- package/src/plugins/builtin/research/analyst-model.ts +37 -2
- package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
- package/src/plugins/builtin/research/index.tsx +5 -8
- package/src/plugins/builtin/research-search/index.tsx +1 -2
- package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
- package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
- package/src/plugins/builtin/roadmap/index.tsx +2 -3
- package/src/plugins/builtin/roadmap/pane.tsx +49 -2
- package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
- package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
- package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
- package/src/plugins/builtin/sec/filing-content.ts +38 -6
- package/src/plugins/builtin/sec/headless.ts +5 -1
- package/src/plugins/builtin/sec/index.tsx +4 -2
- package/src/plugins/builtin/sec/model.ts +2 -0
- package/src/plugins/builtin/sectors/index.tsx +11 -5
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +60 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
- package/src/plugins/builtin/shared/forming-bars.ts +29 -0
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
- package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/pane.tsx +2 -1
- package/src/plugins/builtin/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- package/src/plugins/builtin/supply-chain/client.ts +22 -1
- package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
- package/src/plugins/builtin/supply-chain/index.tsx +1 -2
- package/src/plugins/builtin/thirteenf/api.ts +116 -13
- package/src/plugins/builtin/thirteenf/data.ts +132 -30
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +20 -11
- package/src/plugins/builtin/thirteenf/types.ts +12 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +17 -4
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/headless.ts +11 -3
- package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
- package/src/plugins/builtin/time-sales/index.tsx +1 -2
- package/src/plugins/builtin/time-sales/pane.tsx +3 -1
- package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
- package/src/plugins/builtin/trace-bonds/model.ts +44 -10
- package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
- package/src/plugins/builtin/treasury-auctions/pane.tsx +2 -2
- package/src/plugins/builtin/vol-surface/client.ts +18 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -2
- package/src/plugins/builtin/volatility/index.tsx +1 -2
- package/src/plugins/builtin/world-indices/footer.ts +6 -1
- package/src/plugins/builtin/world-venue-map/client.ts +10 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -2
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
- package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
- package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
- package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
- package/src/plugins/builtin/yield-curve/curves.ts +16 -2
- package/src/plugins/builtin/yield-curve/headless.ts +3 -3
- package/src/plugins/bundle.ts +52 -2
- package/src/plugins/catalog-browser.ts +2 -2
- package/src/plugins/catalog-cli.ts +20 -0
- package/src/plugins/host-link.ts +13 -1
- package/src/plugins/installer.ts +15 -4
- package/src/plugins/loader.ts +43 -11
- package/src/plugins/peer-imports.ts +201 -0
- package/src/plugins/peer-retry.ts +26 -29
- package/src/plugins/plugin-process.ts +128 -0
- package/src/plugins/redirection-guard.ts +45 -0
- package/src/remote/endpoint.ts +16 -3
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/http-proxy.ts +7 -123
- package/src/renderers/dom/desktop/icons.tsx +2 -0
- package/src/renderers/dom/host/image-surface.tsx +46 -21
- package/src/renderers/dom/styles.css +7 -0
- package/src/renderers/dom/toast-host.tsx +2 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +3 -2
- package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
- package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
- package/src/renderers/electrobun/bun/window/create.ts +4 -2
- package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
- package/src/renderers/electrobun/view/main.tsx +7 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/renderers/opentui/browser-open.ts +10 -4
- package/src/renderers/opentui/image/surface.tsx +1 -1
- package/src/renderers/opentui/start.tsx +12 -6
- package/src/renderers/opentui/toast-host.tsx +2 -1
- package/src/series/registry.ts +18 -0
- package/src/series/standalone.ts +10 -1
- package/src/series/types.ts +7 -0
- package/src/series/validate.ts +11 -2
- package/src/sources/gloomberb-cloud/index.ts +28 -5
- package/src/sources/gloomberb-cloud/news.ts +24 -3
- package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
- package/src/sources/provider-errors.ts +26 -3
- package/src/sources/provider-router/history.ts +5 -0
- package/src/sources/provider-router/index.ts +13 -0
- package/src/sync/core-contributors.ts +32 -0
- package/src/telemetry/attention-counts.ts +64 -6
- package/src/telemetry/attention-setting.ts +33 -0
- package/src/telemetry/crash-reports-node.ts +3 -1
- package/src/telemetry/stall-clocks.ts +93 -0
- package/src/telemetry/stall-reports-node.ts +12 -7
- package/src/telemetry/stall-reports.ts +34 -5
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name.ts +19 -0
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +13 -2
- package/src/tickers/search/index.ts +4 -1
- package/src/tickers/search/types.ts +6 -1
- package/src/time-series/bar-aggregation.ts +7 -1
- package/src/time-series/base-series.ts +3 -3
- package/src/time-series/chart-bounds.ts +1 -1
- package/src/time-series/chart-data.ts +5 -2
- package/src/time-series/chart-seed.ts +1 -1
- package/src/time-series/coin-session-close.ts +211 -0
- package/src/time-series/live-bars.ts +13 -2
- package/src/time-series/live-quotes.ts +1 -1
- package/src/time-series/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +55 -6
- package/src/time-series/resolve.ts +12 -3
- package/src/time-series/series-presentation.ts +1 -1
- package/src/time-series/studies/formula.ts +205 -0
- package/src/time-series/studies/indicators.ts +299 -0
- package/src/time-series/studies/leverage.ts +75 -0
- package/src/time-series/studies/pair.ts +138 -0
- package/src/time-series/studies/resolve.ts +333 -0
- package/src/time-series/studies/shared.ts +141 -0
- package/src/time-series/studies/units.ts +243 -0
- package/src/time-series/studies/vwap.ts +274 -0
- package/src/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +22 -0
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +11 -0
- package/src/ui/toast.tsx +26 -1
- package/src/utils/ascii-text.ts +140 -0
- package/src/utils/cli-output.ts +91 -18
- package/src/utils/concurrency.ts +24 -0
- package/src/utils/exchanges.ts +6 -0
- package/src/utils/external-url.ts +20 -7
- package/src/utils/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/main-thread-stall.ts +75 -3
- package/src/utils/perf-marks.ts +36 -2
- package/src/utils/private-files.ts +141 -0
- package/src/utils/public-address.ts +99 -0
- package/src/account/upgrade-personalization.ts +0 -57
- package/src/account/upgrade-tickers.ts +0 -124
- package/src/api-client/upgrade-experiment.ts +0 -91
- package/src/plugins/builtin/cloud/browser.tsx +0 -3
- package/src/renderers/electrobun/launcher/preload.ts +0 -13
- package/src/renderers/electrobun/launcher/windows-emulation.ts +0 -50
- package/src/time-series/studies.ts +0 -1628
|
@@ -23,7 +23,10 @@ const idea = (
|
|
|
23
23
|
definition: { version: 1, currency: "USD", criteria: [...criteria, TRADED], sort },
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Technical ideas, read at the last completed session, then fundamental ones, read on `FA`'s bases (the trailing four
|
|
28
|
+
* quarters, or the latest fiscal year when those quarters are that year) and `EE`'s consensus.
|
|
29
|
+
*/
|
|
27
30
|
export const SCREEN_IDEAS: ScreenIdea[] = [
|
|
28
31
|
idea("oversold-uptrend", "Oversold in an uptrend", [
|
|
29
32
|
{ field: "rsi14", op: "lte", value: 30 },
|
|
@@ -43,6 +46,32 @@ export const SCREEN_IDEAS: ScreenIdea[] = [
|
|
|
43
46
|
{ field: "macdBullishCrossDays", op: "lte", value: 3 },
|
|
44
47
|
{ field: "priceVsSma200Percent", op: "gt", value: 0 },
|
|
45
48
|
], { field: "macdBullishCrossDays", direction: "asc" }),
|
|
49
|
+
// A free cash flow yield of 6% or more, about twice the S&P 500's; EV / EBITDA of 10x or less, under the index's
|
|
50
|
+
// long-run 12x or so; net debt within 3x EBITDA, about where investment grade ends, so a cheap price is not a
|
|
51
|
+
// stretched balance sheet. Highest yield first.
|
|
52
|
+
idea("value", "Value", [
|
|
53
|
+
{ field: "fcfYieldPercent", op: "gte", value: 6 },
|
|
54
|
+
{ field: "evToEbitda", op: "lte", value: 10 },
|
|
55
|
+
{ field: "netDebtToEbitda", op: "lte", value: 3 },
|
|
56
|
+
], { field: "fcfYieldPercent", direction: "desc" }),
|
|
57
|
+
// A return on invested capital of 15% or more, above any company's cost of capital; net debt within 2x EBITDA;
|
|
58
|
+
// revenue compounding at 5% a year or more over five years; free cash flow above zero. Highest return first.
|
|
59
|
+
idea("quality", "Quality", [
|
|
60
|
+
{ field: "roicPercent", op: "gte", value: 15 },
|
|
61
|
+
{ field: "netDebtToEbitda", op: "lte", value: 2 },
|
|
62
|
+
{ field: "revenueCagr5YPercent", op: "gte", value: 5 },
|
|
63
|
+
{ field: "fcfYieldPercent", op: "gt", value: 0 },
|
|
64
|
+
], { field: "roicPercent", direction: "desc" }),
|
|
65
|
+
// Growth at a reasonable price: consensus EPS growth of 10% to 25% for the fiscal year in progress (Peter Lynch's
|
|
66
|
+
// range; faster rarely lasts) on a three-year record of 10% a year or more; a forward P/E of 20x or less, so the
|
|
67
|
+
// price stays within twice the growth; ROIC of 10% or more, so the growth is earned, not a recovery from a loss.
|
|
68
|
+
// Fastest growth first.
|
|
69
|
+
idea("garp", "GARP", [
|
|
70
|
+
{ field: "forwardEpsGrowthPercent", op: "between", value: [10, 25] },
|
|
71
|
+
{ field: "epsCagr3YPercent", op: "gte", value: 10 },
|
|
72
|
+
{ field: "forwardPE", op: "lte", value: 20 },
|
|
73
|
+
{ field: "roicPercent", op: "gte", value: 10 },
|
|
74
|
+
], { field: "forwardEpsGrowthPercent", direction: "desc" }),
|
|
46
75
|
];
|
|
47
76
|
|
|
48
77
|
export const screenIdea = (id: string | undefined): ScreenIdea | undefined =>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { PluginModule } from "../plugin-module";
|
|
2
2
|
import { equityScreenerHeadless } from "./headless";
|
|
3
|
-
import { EquityScreenerPane } from "./pane";
|
|
4
3
|
|
|
5
4
|
export const equityScreenerModule: PluginModule = {
|
|
6
5
|
panes: [
|
|
@@ -8,7 +7,7 @@ export const equityScreenerModule: PluginModule = {
|
|
|
8
7
|
id: "equity-screener",
|
|
9
8
|
name: "Equity Screener",
|
|
10
9
|
icon: "S",
|
|
11
|
-
component: EquityScreenerPane,
|
|
10
|
+
get component(): typeof import("./pane").EquityScreenerPane { const { EquityScreenerPane } = require("./pane"); return EquityScreenerPane; },
|
|
12
11
|
defaultPosition: "right",
|
|
13
12
|
defaultMode: "floating",
|
|
14
13
|
defaultFloatingSize: { width: 108, height: 30 },
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
NUMERIC_FIELDS,
|
|
7
7
|
OPTION_VOLATILITY_FIELDS,
|
|
8
8
|
OWNERSHIP_FIELDS,
|
|
9
|
+
QUALITY_FIELDS,
|
|
9
10
|
RESEARCH_FIELDS,
|
|
10
11
|
KPI_FIELDS,
|
|
11
12
|
SOCIAL_FIELDS,
|
|
@@ -126,6 +127,16 @@ export const SHORT_LABELS: Record<NumericField, string> = {
|
|
|
126
127
|
floatShares: "FLOAT",
|
|
127
128
|
shortPercentOfFloat: "SI % FLOAT",
|
|
128
129
|
institutionalOwnershipPercent: "INST OWN%",
|
|
130
|
+
roicPercent: "ROIC%",
|
|
131
|
+
fcfYieldPercent: "FCF YLD%",
|
|
132
|
+
netDebtToEbitda: "ND/EBITDA",
|
|
133
|
+
debtToEquity: "D/E",
|
|
134
|
+
revenueCagr3YPercent: "REV CAGR 3Y%",
|
|
135
|
+
revenueCagr5YPercent: "REV CAGR 5Y%",
|
|
136
|
+
epsCagr3YPercent: "EPS CAGR 3Y%",
|
|
137
|
+
epsCagr5YPercent: "EPS CAGR 5Y%",
|
|
138
|
+
forwardRevenueGrowthPercent: "FWD REV GR%",
|
|
139
|
+
forwardEpsGrowthPercent: "FWD EPS GR%",
|
|
129
140
|
hiringOpenRoles: "OPEN ROLES",
|
|
130
141
|
hiringNetAddsWeek: "NET ROLES/WK",
|
|
131
142
|
hiringRemotePercent: "REMOTE%",
|
|
@@ -138,7 +149,8 @@ export const SHORT_LABELS: Record<NumericField, string> = {
|
|
|
138
149
|
const COMPACT = new Set<NumericField>(["kpiArr", "kpiRpo", "kpiCrpo", "kpiSubscribers", "kpiDau", "kpiMau", "kpiGmv", "kpiStores", "kpiBacklog", "marketCap", "volume", "averageVolume20d", "shortInterestShares", "sharesOutstanding", "floatShares"]);
|
|
139
150
|
/** Daily counts: whole numbers below a thousand, compact above. */
|
|
140
151
|
const DAILY_COUNTS = new Set<NumericField>(["xPostsPerDay", "wikiViewsPerDay"]);
|
|
141
|
-
const RATIOS = new Set<NumericField>(["kpiBookToBill", "xPostsVsMedian", "wikiViewsVsMedian", "priceToBook", "evToEbitda", "ivToHv"
|
|
152
|
+
const RATIOS = new Set<NumericField>(["kpiBookToBill", "xPostsVsMedian", "wikiViewsVsMedian", "priceToBook", "evToEbitda", "ivToHv",
|
|
153
|
+
"netDebtToEbitda", "debtToEquity"]);
|
|
142
154
|
const RETURNS: NumericField[] = ["return1WPercent", "return1MPercent", "return3MPercent", "returnYtdPercent", "return1YPercent"];
|
|
143
155
|
const SIGNED = new Set<NumericField>(["hiringNetAddsWeek", "hiringZScore", "appRankVelocity7d", "appRatingDrift7d", "appRatingCountGrowth7d",
|
|
144
156
|
"guideRevenueChangePercent", "guideEpsChangePercent", "kpiSameStoreSalesPercent",
|
|
@@ -147,6 +159,8 @@ const SIGNED = new Set<NumericField>(["hiringNetAddsWeek", "hiringZScore", "appR
|
|
|
147
159
|
...RETURNS, "fromHigh52WPercent", "epsRevision30dPercent", "analystUpsidePercent",
|
|
148
160
|
"priceVsSma20Percent", "priceVsSma50Percent", "priceVsSma200Percent",
|
|
149
161
|
"iv30Change1d", "iv30Change5d",
|
|
162
|
+
"revenueCagr3YPercent", "revenueCagr5YPercent", "epsCagr3YPercent", "epsCagr5YPercent",
|
|
163
|
+
"forwardRevenueGrowthPercent", "forwardEpsGrowthPercent",
|
|
150
164
|
]);
|
|
151
165
|
/** Moves whose sign is the news: they take the sign colour. */
|
|
152
166
|
export const SIGN_COLORED = new Set<NumericField>(["changePercent", ...RETURNS, "epsRevision30dPercent", "analystUpsidePercent",
|
|
@@ -369,6 +383,16 @@ export const screenLabel = (field: string) =>
|
|
|
369
383
|
floatShares: "Float",
|
|
370
384
|
shortPercentOfFloat: "Short interest % of float",
|
|
371
385
|
institutionalOwnershipPercent: "Institutional ownership % (13F)",
|
|
386
|
+
roicPercent: "ROIC %",
|
|
387
|
+
fcfYieldPercent: "FCF yield %",
|
|
388
|
+
netDebtToEbitda: "Net debt / EBITDA",
|
|
389
|
+
debtToEquity: "Debt / equity",
|
|
390
|
+
revenueCagr3YPercent: "Revenue CAGR 3Y %",
|
|
391
|
+
revenueCagr5YPercent: "Revenue CAGR 5Y %",
|
|
392
|
+
epsCagr3YPercent: "EPS CAGR 3Y %",
|
|
393
|
+
epsCagr5YPercent: "EPS CAGR 5Y %",
|
|
394
|
+
forwardRevenueGrowthPercent: "Forward revenue growth %",
|
|
395
|
+
forwardEpsGrowthPercent: "Forward EPS growth %",
|
|
372
396
|
hiringOpenRoles: "Hiring open roles",
|
|
373
397
|
hiringNetAddsWeek: "Net open roles per week",
|
|
374
398
|
hiringRemotePercent: "Remote roles %",
|
|
@@ -682,9 +706,9 @@ export function validateScreenPayload(value: unknown): ScreenPayload {
|
|
|
682
706
|
throw new Error("Invalid screener listing identity.");
|
|
683
707
|
ids.add(screenRowId(row));
|
|
684
708
|
for (const field of NUMERIC_FIELDS) {
|
|
685
|
-
// A server from before social, research, volatility, dividend, technical, option volatility or
|
|
686
|
-
// omits them; they read as unavailable. A Pro field it left out was held back from this caller.
|
|
687
|
-
if (!row.metrics[field] && ([...SOCIAL_FIELDS, ...RESEARCH_FIELDS, ...VOLATILITY_FIELDS, ...TECHNICAL_FIELDS, ...OPTION_VOLATILITY_FIELDS, ...OWNERSHIP_FIELDS, ...ATTENTION_FIELDS, ...HIRING_APP_FIELDS, ...KPI_FIELDS, ...DIVIDEND_FIELDS] as readonly string[]).includes(field))
|
|
709
|
+
// A server from before social, research, volatility, dividend, technical, option volatility, ownership or quality
|
|
710
|
+
// fields omits them; they read as unavailable. A Pro field it left out was held back from this caller.
|
|
711
|
+
if (!row.metrics[field] && ([...SOCIAL_FIELDS, ...RESEARCH_FIELDS, ...VOLATILITY_FIELDS, ...TECHNICAL_FIELDS, ...OPTION_VOLATILITY_FIELDS, ...OWNERSHIP_FIELDS, ...QUALITY_FIELDS, ...ATTENTION_FIELDS, ...HIRING_APP_FIELDS, ...KPI_FIELDS, ...DIVIDEND_FIELDS] as readonly string[]).includes(field))
|
|
688
712
|
row.metrics[field] = PRO_FIELDS.has(field) ? withheldMetric() : unavailableMetric();
|
|
689
713
|
const metric = row.metrics[field];
|
|
690
714
|
if (
|
|
@@ -52,6 +52,7 @@ import { useResearchCloudSession } from "../shared/research-cloud-session";
|
|
|
52
52
|
import { INITIAL_STREAM_RANGE, streamWindowRows } from "../shared/use-quote-board";
|
|
53
53
|
import { useSampledValue } from "../../../state/hooks/live-ticker-financials";
|
|
54
54
|
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
55
|
+
import { useQuoteDelayFooter } from "../shared/cloud-upgrade";
|
|
55
56
|
import { SignInWall } from "../cloud/auth-actions";
|
|
56
57
|
import { CriterionEditor } from "./criterion-editor";
|
|
57
58
|
import { SCREEN_IDEAS, screenIdea } from "./ideas";
|
|
@@ -584,6 +585,8 @@ function EquityScreenView({
|
|
|
584
585
|
disabled: !selected,
|
|
585
586
|
},
|
|
586
587
|
];
|
|
588
|
+
// Signed out or free, the quotes streamed over the snapshot's rows are delayed ones.
|
|
589
|
+
const quoteDelay = useQuoteDelayFooter(Array.from(liveEntries.values(), (entry) => entry.data ?? entry.lastGoodData), "screener-delay");
|
|
587
590
|
usePaneFooter(
|
|
588
591
|
"equity-screener",
|
|
589
592
|
() => check ? null : ({
|
|
@@ -632,10 +635,12 @@ function EquityScreenView({
|
|
|
632
635
|
},
|
|
633
636
|
]
|
|
634
637
|
: []),
|
|
638
|
+
...(quoteDelay ? [quoteDelay] : []),
|
|
635
639
|
],
|
|
636
640
|
hints,
|
|
637
641
|
}),
|
|
638
642
|
[
|
|
643
|
+
quoteDelay,
|
|
639
644
|
results.loading,
|
|
640
645
|
results.loadingMore,
|
|
641
646
|
data,
|
|
@@ -75,12 +75,34 @@ export function screenCheckSummary(data: ScreenCheckPayload): string {
|
|
|
75
75
|
return data.matches === null ? "Not covered" : data.matches ? "Passes all criteria" : "Does not match";
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/** The thresholds a criterion compares a value with: one, or a range's two ends. */
|
|
79
|
+
function thresholds(criterion: ScreenCriterion): number[] {
|
|
80
|
+
if (!("value" in criterion)) return [];
|
|
81
|
+
const value = criterion.value as unknown;
|
|
82
|
+
return (Array.isArray(value) ? value : [value]).filter((entry): entry is number => typeof entry === "number");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A stored value as DVD writes its figures: two decimals (6.93%, 26.42 x), a
|
|
87
|
+
* whole number without them. A value that would round onto a threshold it is
|
|
88
|
+
* not (25.0001 against <= 25) keeps the decimals that tell it apart, so its
|
|
89
|
+
* Fail never reads as 25.00 failing <= 25.
|
|
90
|
+
*/
|
|
91
|
+
function checkValueText(value: number, unit: string, criterion: ScreenCriterion): string {
|
|
92
|
+
const limits = thresholds(criterion);
|
|
93
|
+
const whole = Number.isInteger(value) && unit !== "%";
|
|
94
|
+
let digits = whole ? 0 : 2;
|
|
95
|
+
while (digits < 15 && limits.some((limit) => limit !== value && Number(value.toFixed(digits)) === limit)) digits += 1;
|
|
96
|
+
const text = value.toLocaleString("en-US", { minimumFractionDigits: whole ? 0 : 2, maximumFractionDigits: digits });
|
|
97
|
+
return unit === "%" ? `${text}%` : `${text}${unit ? ` ${unit}` : ""}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
78
100
|
export function checkRows(data: ScreenCheckPayload) {
|
|
79
101
|
return data.criteria.map((entry, index) => ({
|
|
80
102
|
id: String(index),
|
|
81
103
|
criterion: checkCriterionText(entry.criterion),
|
|
82
104
|
value: entry.state === "not-covered" ? "Not covered" : entry.value === null ? "Missing" : typeof entry.value === "number"
|
|
83
|
-
?
|
|
105
|
+
? checkValueText(entry.value, entry.unit, entry.criterion) : entry.value,
|
|
84
106
|
result: entry.passes === null ? "Not evaluated" : entry.passes ? "Pass" : "Fail",
|
|
85
107
|
asOf: entry.asOf?.slice(0, 10) ?? (entry.observedAt ? `${entry.observedAt.slice(0, 10)} (collected)` : "--"),
|
|
86
108
|
state: entry.state,
|
|
@@ -3,7 +3,6 @@ import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
|
|
|
3
3
|
import { estimateRevisionsCache } from "./client";
|
|
4
4
|
import { estimateRevisionsHeadless } from "./headless";
|
|
5
5
|
import { estimateRevisionsLockNote } from "./lock-note";
|
|
6
|
-
import { EstimateRevisionsPane } from "./pane";
|
|
7
6
|
export const estimateRevisionsModule: PluginModule = {
|
|
8
7
|
panes: [
|
|
9
8
|
{
|
|
@@ -12,7 +11,7 @@ export const estimateRevisionsModule: PluginModule = {
|
|
|
12
11
|
reportRequirements: ["Free Gloom sign-in"],
|
|
13
12
|
name: "Estimate Revisions",
|
|
14
13
|
icon: "E",
|
|
15
|
-
component: EstimateRevisionsPane,
|
|
14
|
+
get component(): typeof import("./pane").EstimateRevisionsPane { const { EstimateRevisionsPane } = require("./pane"); return EstimateRevisionsPane; },
|
|
16
15
|
defaultPosition: "right",
|
|
17
16
|
tickerFollower: true,
|
|
18
17
|
instruments: ["equity"],
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExposureHolding } from "../../../api-client/exposure";
|
|
2
2
|
import type { DataProvider } from "../../../types/data-provider";
|
|
3
|
+
import type { Quote } from "../../../types/financials";
|
|
3
4
|
import type { Portfolio, TickerRecord } from "../../../types/ticker";
|
|
4
5
|
import { publicTickerKey } from "../../../utils/exchanges";
|
|
5
6
|
import { resolveCurrencyUnit } from "../../../utils/currency-units";
|
|
@@ -7,8 +8,25 @@ import { convertCurrency } from "../../../utils/format";
|
|
|
7
8
|
import { logFailure } from "../../../utils/log-failure";
|
|
8
9
|
import { getPortfolioPositionMetrics, getPortfolioQuoteDisplay, resolvePortfolioMarketValue } from "../../../portfolio/position-metrics";
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Every position's quote: one batch where the provider has one, else one
|
|
13
|
+
* request per position, all at once. A position without a quote is null.
|
|
14
|
+
*/
|
|
15
|
+
async function positionQuotes(positions: TickerRecord[], market: Pick<DataProvider, "getQuote" | "getQuotesBatch">): Promise<Array<Quote | null>> {
|
|
16
|
+
const failed = logFailure("exposure.holdings", "Load portfolio holding quote failed", null);
|
|
17
|
+
if (market.getQuotesBatch) {
|
|
18
|
+
const targets = positions.map((ticker) => ({ symbol: ticker.metadata.ticker, exchange: ticker.metadata.exchange, surface: "portfolio" as const }));
|
|
19
|
+
const results = await market.getQuotesBatch(targets).catch((error: unknown) => {
|
|
20
|
+
failed(error);
|
|
21
|
+
return [];
|
|
22
|
+
});
|
|
23
|
+
return positions.map((_, index) => results[index]?.quote ?? null);
|
|
24
|
+
}
|
|
25
|
+
return Promise.all(positions.map((ticker) => market.getQuote(ticker.metadata.ticker, ticker.metadata.exchange).catch(failed)));
|
|
26
|
+
}
|
|
27
|
+
|
|
10
28
|
/** Explicit NAV preserves cash, shorts and leverage; net holdings are never used as NAV. */
|
|
11
|
-
export async function portfolioHoldings(portfolio: Portfolio, tickers: TickerRecord[], nav: number, market: Pick<DataProvider, "getQuote" | "getExchangeRate">): Promise<ExposureHolding[]> {
|
|
29
|
+
export async function portfolioHoldings(portfolio: Portfolio, tickers: TickerRecord[], nav: number, market: Pick<DataProvider, "getQuote" | "getQuotesBatch" | "getExchangeRate">): Promise<ExposureHolding[]> {
|
|
12
30
|
if (!Number.isFinite(nav) || nav <= 0) throw new Error(`Enter positive NAV in ${portfolio.currency} for ${portfolio.name}.`);
|
|
13
31
|
const positions = tickers.filter(t => t.metadata.positions.some(p => p.portfolio === portfolio.id && p.shares !== 0));
|
|
14
32
|
if (!positions.length) throw new Error("The selected portfolio has no positions.");
|
|
@@ -21,25 +39,29 @@ export async function portfolioHoldings(portfolio: Portfolio, tickers: TickerRec
|
|
|
21
39
|
t.metadata.positions.filter(p => p.portfolio === portfolio.id).forEach(p => { if (p.currency) currencies.add(p.currency); });
|
|
22
40
|
}
|
|
23
41
|
const rates = new Map<string, number>([["USD", 1]]);
|
|
24
|
-
|
|
42
|
+
// One request per major currency, however many of its units (GBP, GBp) ask at once.
|
|
43
|
+
const majorRates = new Map<string, Promise<number>>([["USD", Promise.resolve(1)]]);
|
|
25
44
|
const ensureRate = async (rawCurrency: string) => {
|
|
26
45
|
const unit = resolveCurrencyUnit(rawCurrency);
|
|
27
46
|
if (!unit.currency) throw new Error("Portfolio currency is missing.");
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
47
|
+
const major = unit.currency;
|
|
48
|
+
let pending = majorRates.get(major);
|
|
49
|
+
if (!pending) {
|
|
50
|
+
pending = market.getExchangeRate(major).then((rate) => {
|
|
51
|
+
if (!Number.isFinite(rate) || rate <= 0) throw new Error(`FX unavailable for ${major}; weights were not estimated.`);
|
|
52
|
+
return rate;
|
|
53
|
+
});
|
|
54
|
+
majorRates.set(major, pending);
|
|
33
55
|
}
|
|
34
|
-
rates.set(rawCurrency,
|
|
56
|
+
rates.set(rawCurrency, (await pending) / unit.divisor);
|
|
35
57
|
};
|
|
36
|
-
|
|
58
|
+
const [quotes] = await Promise.all([positionQuotes(positions, market), Promise.all([...currencies].map(ensureRate))]);
|
|
59
|
+
// A quote in a currency the positions did not name.
|
|
60
|
+
await Promise.all([...new Set(quotes.flatMap((quote) => quote?.currency && !rates.has(quote.currency) ? [quote.currency] : []))].map(ensureRate));
|
|
37
61
|
const holdings: ExposureHolding[] = [];
|
|
38
|
-
for (const ticker of positions) {
|
|
39
|
-
const quote =
|
|
40
|
-
.catch(logFailure("exposure.holdings", "Load portfolio holding quote failed", null));
|
|
62
|
+
for (const [index, ticker] of positions.entries()) {
|
|
63
|
+
const quote = quotes[index] ?? null;
|
|
41
64
|
const currency = quote?.currency || ticker.metadata.currency;
|
|
42
|
-
if (!rates.has(currency)) await ensureRate(currency);
|
|
43
65
|
const metrics = getPortfolioPositionMetrics(ticker, portfolio.id, currency, {
|
|
44
66
|
currency: portfolio.currency, convert: (value, from) => convertCurrency(value, from, portfolio.currency, rates),
|
|
45
67
|
}, quote);
|
|
@@ -2,7 +2,6 @@ import type { PaneTemplateContext, ShortcutArgError } from "../../../types/plugi
|
|
|
2
2
|
import { findNamedCollection, unknownCollectionArgError } from "../../../portfolio/collection-name";
|
|
3
3
|
import type { PluginModule } from "../plugin-module";
|
|
4
4
|
import { exposureHeadless } from "./headless";
|
|
5
|
-
import { ExposurePane } from "./pane";
|
|
6
5
|
|
|
7
6
|
const SOURCE = /^(PORT|WATCH):(.+)$/i;
|
|
8
7
|
|
|
@@ -29,7 +28,7 @@ function unknownSourceArg(arg: string, context: PaneTemplateContext): ShortcutAr
|
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
export const exposureModule: PluginModule = {
|
|
32
|
-
panes: [{ id: "exposure", name: "Exposure", icon: "E", component: ExposurePane, defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 138, height: 31 }, tableExport: true, headless: exposureHeadless,
|
|
31
|
+
panes: [{ id: "exposure", name: "Exposure", icon: "E", get component(): typeof import("./pane").ExposurePane { const { ExposurePane } = require("./pane"); return ExposurePane; }, defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 138, height: 31 }, tableExport: true, headless: exposureHeadless,
|
|
33
32
|
personalData: "holdings",
|
|
34
33
|
portableShare: { private: { params: true, settings: true, state: true } } }],
|
|
35
34
|
paneTemplates: [{ id: "exposure-pane", paneId: "exposure", label: "Exposure", description: "Pro scenario exposures across holdings, geographic revenue and supply chains, with source evidence and portfolio concentrations.",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CLI_COMMAND_GROUPS } from "../../../cli/help";
|
|
2
|
+
import { rowsFreshness } from "../../../cli/freshness";
|
|
3
|
+
import { humanizeCliKey } from "../../../cli/result";
|
|
4
|
+
import type { CliCommandDef } from "../../../types/plugin";
|
|
5
|
+
import { loadFearGreed } from "./cache";
|
|
6
|
+
|
|
7
|
+
const SCORE_KEYS = new Set(["score", "previousClose", "previousWeek", "previousMonth", "previousYear"]);
|
|
8
|
+
/** A score to a tenth of a point (17.9); a missing one leaves its cell empty, as before. */
|
|
9
|
+
const scoreTenths = (value: unknown) => typeof value === "number" && Number.isFinite(value) ? value.toFixed(1) : "";
|
|
10
|
+
|
|
11
|
+
// Without the pane cache attached, this fetches a fresh reading for the command.
|
|
12
|
+
export const fearGreedCliCommand: CliCommandDef = {
|
|
13
|
+
name: "fear-greed",
|
|
14
|
+
description: "Fetch CNN Fear & Greed gauge data",
|
|
15
|
+
help: { group: CLI_COMMAND_GROUPS.markets },
|
|
16
|
+
async execute(_args, ctx) {
|
|
17
|
+
const { data, stale } = await loadFearGreed(ctx.cliOptions.refresh);
|
|
18
|
+
const rows = [{
|
|
19
|
+
score: data.overall.score,
|
|
20
|
+
rating: data.overall.rating,
|
|
21
|
+
updatedAt: data.overall.updatedAt?.toISOString() ?? "",
|
|
22
|
+
previousClose: data.overall.previousClose,
|
|
23
|
+
previousWeek: data.overall.previousWeek,
|
|
24
|
+
previousMonth: data.overall.previousMonth,
|
|
25
|
+
previousYear: data.overall.previousYear,
|
|
26
|
+
}];
|
|
27
|
+
ctx.printResult({
|
|
28
|
+
data: rows,
|
|
29
|
+
// Exports cite CNN's reading as `fn FNG` does; the text stays the row alone.
|
|
30
|
+
exportFreshness: rowsFreshness(rows, { source: "CNN", status: stale ? "stale" : "not-a-feed", basis: "published index" }),
|
|
31
|
+
metadata: {
|
|
32
|
+
indicators: data.indicators.map((indicator) => ({
|
|
33
|
+
id: indicator.definition.id,
|
|
34
|
+
score: indicator.score,
|
|
35
|
+
rating: indicator.rating,
|
|
36
|
+
})),
|
|
37
|
+
},
|
|
38
|
+
}, {
|
|
39
|
+
// Scores read to a tenth of a point on the text table; exports keep every digit.
|
|
40
|
+
textColumns: Object.keys(rows[0]!).map((key) => ({ key, header: humanizeCliKey(key),
|
|
41
|
+
...(SCORE_KEYS.has(key) ? { align: "right" as const, format: scoreTenths } : {}) })),
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -1,26 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { humanizeCliKey } from "../../../cli/result";
|
|
1
|
+
import { fearGreedPluginMeta } from "../builtin-plugin-meta";
|
|
2
|
+
import { fearGreedCliCommand } from "./cli";
|
|
4
3
|
import type { GloomPlugin } from "../../../types/plugin";
|
|
5
4
|
import {
|
|
6
5
|
attachFearGreedPersistence,
|
|
7
|
-
loadFearGreed,
|
|
8
6
|
resetFearGreedPersistence,
|
|
9
7
|
} from "./cache";
|
|
10
|
-
import { FearGreedPane } from "./pane";
|
|
11
8
|
import { fearGreedHeadless } from "./headless";
|
|
12
9
|
import { FEAR_GREED_SERIES } from "./series";
|
|
13
10
|
|
|
14
|
-
const SCORE_KEYS = new Set(["score", "previousClose", "previousWeek", "previousMonth", "previousYear"]);
|
|
15
|
-
/** A score to a tenth of a point (17.9); a missing one leaves its cell empty, as before. */
|
|
16
|
-
const scoreTenths = (value: unknown) => typeof value === "number" && Number.isFinite(value) ? value.toFixed(1) : "";
|
|
17
|
-
|
|
18
11
|
export const fearGreedPlugin: GloomPlugin = {
|
|
19
|
-
|
|
20
|
-
name: "Fear & Greed",
|
|
21
|
-
version: "1.0.0",
|
|
22
|
-
description: "CNN Fear & Greed index with the seven indicator charts",
|
|
23
|
-
toggleable: true,
|
|
12
|
+
...fearGreedPluginMeta,
|
|
24
13
|
|
|
25
14
|
// One JSON endpoint over HTTPS, so every renderer. CNN sends no CORS
|
|
26
15
|
// headers and wants a Referer, which is why the host is declared: the web
|
|
@@ -44,7 +33,7 @@ export const fearGreedPlugin: GloomPlugin = {
|
|
|
44
33
|
id: "fear-greed",
|
|
45
34
|
name: "Fear & Greed",
|
|
46
35
|
icon: "G",
|
|
47
|
-
component: FearGreedPane,
|
|
36
|
+
get component(): typeof import("./pane").FearGreedPane { const { FearGreedPane } = require("./pane"); return FearGreedPane; },
|
|
48
37
|
defaultPosition: "right",
|
|
49
38
|
defaultMode: "floating",
|
|
50
39
|
defaultFloatingSize: { width: 110, height: 36 },
|
|
@@ -63,42 +52,5 @@ export const fearGreedPlugin: GloomPlugin = {
|
|
|
63
52
|
},
|
|
64
53
|
],
|
|
65
54
|
|
|
66
|
-
|
|
67
|
-
// without the pane cache attached when invoked from the CLI, so it always
|
|
68
|
-
// fetches, which is what a CLI call wants anyway.
|
|
69
|
-
cliCommands: [
|
|
70
|
-
{
|
|
71
|
-
name: "fear-greed",
|
|
72
|
-
description: "Fetch CNN Fear & Greed gauge data",
|
|
73
|
-
help: { group: CLI_COMMAND_GROUPS.markets },
|
|
74
|
-
async execute(_args, ctx) {
|
|
75
|
-
const { data, stale } = await loadFearGreed(ctx.cliOptions.refresh);
|
|
76
|
-
const rows = [{
|
|
77
|
-
score: data.overall.score,
|
|
78
|
-
rating: data.overall.rating,
|
|
79
|
-
updatedAt: data.overall.updatedAt?.toISOString() ?? "",
|
|
80
|
-
previousClose: data.overall.previousClose,
|
|
81
|
-
previousWeek: data.overall.previousWeek,
|
|
82
|
-
previousMonth: data.overall.previousMonth,
|
|
83
|
-
previousYear: data.overall.previousYear,
|
|
84
|
-
}];
|
|
85
|
-
ctx.printResult({
|
|
86
|
-
data: rows,
|
|
87
|
-
// Exports cite CNN's reading as `fn FNG` does; the text stays the row alone.
|
|
88
|
-
exportFreshness: rowsFreshness(rows, { source: "CNN", status: stale ? "stale" : "not-a-feed", basis: "published index" }),
|
|
89
|
-
metadata: {
|
|
90
|
-
indicators: data.indicators.map((indicator) => ({
|
|
91
|
-
id: indicator.definition.id,
|
|
92
|
-
score: indicator.score,
|
|
93
|
-
rating: indicator.rating,
|
|
94
|
-
})),
|
|
95
|
-
},
|
|
96
|
-
}, {
|
|
97
|
-
// Scores read to a tenth of a point on the text table; exports keep every digit.
|
|
98
|
-
textColumns: Object.keys(rows[0]!).map((key) => ({ key, header: humanizeCliKey(key),
|
|
99
|
-
...(SCORE_KEYS.has(key) ? { align: "right" as const, format: scoreTenths } : {}) })),
|
|
100
|
-
});
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
],
|
|
55
|
+
cliCommands: [fearGreedCliCommand],
|
|
104
56
|
};
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
usePaneStatusFooter,
|
|
9
9
|
type StatItem,
|
|
10
10
|
} from "../../../components";
|
|
11
|
+
import { ageFooterSegment } from "../../../components/layout/pane/footer";
|
|
11
12
|
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
12
13
|
import { useAsyncResource } from "../../../react/async-resource";
|
|
13
14
|
import type { PaneProps } from "../../../types/plugin";
|
|
@@ -61,7 +62,7 @@ export function FearGreedPane({ paneId, focused, width, height }: PaneProps) {
|
|
|
61
62
|
],
|
|
62
63
|
}] : []),
|
|
63
64
|
...(stale ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []),
|
|
64
|
-
...(updatedAgo ? [{ id: "age",
|
|
65
|
+
...(updatedAgo ? [ageFooterSegment(updatedAgo, { id: "age", verb: "updated", tone: "value" })] : []),
|
|
65
66
|
], [data, stale, updatedAgo]);
|
|
66
67
|
usePaneStatusFooter({ registrationId: paneId, loading, error, info: footerInfo });
|
|
67
68
|
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import type { PluginModule } from "../plugin-module";
|
|
2
2
|
import { treasuryAuctionsSettings } from "../treasury-auctions";
|
|
3
3
|
import { FIXED_INCOME_PANE_ID } from "./model";
|
|
4
|
-
import { FixedIncomePane } from "./pane";
|
|
5
4
|
|
|
6
5
|
export const fixedIncomeModule: PluginModule = {
|
|
7
6
|
panes: [{
|
|
8
7
|
id: FIXED_INCOME_PANE_ID,
|
|
9
8
|
name: "Fixed Income",
|
|
10
9
|
icon: "B",
|
|
11
|
-
component: FixedIncomePane,
|
|
10
|
+
get component(): typeof import("./pane").FixedIncomePane { const { FixedIncomePane } = require("./pane"); return FixedIncomePane; },
|
|
12
11
|
defaultPosition: "right",
|
|
13
12
|
defaultMode: "floating",
|
|
14
13
|
defaultFloatingSize: { width: 116, height: 32 },
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createConcurrencyLimiter } from "../../../utils/concurrency";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The board's contract histories (use-front-returns) and contract terms
|
|
5
|
+
* (use-board-terms) share one limit of twelve reads at a time, across both and
|
|
6
|
+
* every board open: seventy-odd rows load in about a second without the two
|
|
7
|
+
* crowding out each other or the quotes.
|
|
8
|
+
*/
|
|
9
|
+
const BOARD_READ_LIMIT = 12;
|
|
10
|
+
|
|
11
|
+
export const boardRead = createConcurrencyLimiter(BOARD_READ_LIMIT);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Reads that reach a source sending reads asked together as batch requests
|
|
15
|
+
* skip that limit: asked all at once, the API client sends a board's rows
|
|
16
|
+
* sixteen a request. This bound only keeps a runaway caller in check.
|
|
17
|
+
*/
|
|
18
|
+
const BOARD_BATCHED_READ_LIMIT = 200;
|
|
19
|
+
|
|
20
|
+
const boardBatchedRead = createConcurrencyLimiter(BOARD_BATCHED_READ_LIMIT);
|
|
21
|
+
|
|
22
|
+
/** The gate for the board's reads: twelve at a time, unless they go out in batch requests. */
|
|
23
|
+
export function boardReadGate(batched: boolean): typeof boardRead {
|
|
24
|
+
return batched ? boardBatchedRead : boardRead;
|
|
25
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** How long reads landing one after another are gathered into one redraw. */
|
|
2
|
+
const LANDING_GATHER_MS = 100;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A redraw for reads that land one by one (a board's seventy contract
|
|
6
|
+
* histories): the first landing redraws at once, and those in the next
|
|
7
|
+
* `LANDING_GATHER_MS` are drawn together when it ends, so the board redraws a
|
|
8
|
+
* few times rather than once per read. `flush` draws what is waiting at once
|
|
9
|
+
* (the last read landed) and `cancel` drops it (the board moved on).
|
|
10
|
+
*/
|
|
11
|
+
export function coalescedBump(redraw: () => void, gatherMs = LANDING_GATHER_MS) {
|
|
12
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
13
|
+
let waiting = false;
|
|
14
|
+
const gather = () => {
|
|
15
|
+
timer = setTimeout(() => {
|
|
16
|
+
timer = undefined;
|
|
17
|
+
if (!waiting) return;
|
|
18
|
+
waiting = false;
|
|
19
|
+
redraw();
|
|
20
|
+
gather();
|
|
21
|
+
}, gatherMs);
|
|
22
|
+
};
|
|
23
|
+
const cancel = () => {
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
timer = undefined;
|
|
26
|
+
waiting = false;
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
bump() {
|
|
30
|
+
if (timer !== undefined) {
|
|
31
|
+
waiting = true;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
redraw();
|
|
35
|
+
gather();
|
|
36
|
+
},
|
|
37
|
+
flush() {
|
|
38
|
+
const draw = waiting;
|
|
39
|
+
cancel();
|
|
40
|
+
if (draw) redraw();
|
|
41
|
+
},
|
|
42
|
+
cancel,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -25,9 +25,11 @@ import type { PluginModule } from "../plugin-module";
|
|
|
25
25
|
import { FUTURES_SERIES } from "./series";
|
|
26
26
|
import { useLiveStreamingSetting } from "../../../state/hooks/live-streaming";
|
|
27
27
|
import { quoteObservation } from "../shared/report-freshness";
|
|
28
|
+
import { useQuoteDelayFooter } from "../shared/cloud-upgrade";
|
|
28
29
|
import {
|
|
29
30
|
boardErrorMessage,
|
|
30
31
|
boardHeadlineQuote,
|
|
32
|
+
boardQuotes,
|
|
31
33
|
quoteBoardFooterInfo,
|
|
32
34
|
quoteBoardStatus,
|
|
33
35
|
useQuoteBoard,
|
|
@@ -46,6 +48,7 @@ import {
|
|
|
46
48
|
DEFAULT_FUTURES_SORT,
|
|
47
49
|
effectiveCollapsedSectors,
|
|
48
50
|
FUTURES_RETURN_COLUMNS,
|
|
51
|
+
futuresNoMatchFailure,
|
|
49
52
|
futuresRowId,
|
|
50
53
|
type FuturesColumnId,
|
|
51
54
|
type FuturesSortPreference,
|
|
@@ -255,15 +258,26 @@ export function FuturesPane({ focused, width, height }: PaneProps) {
|
|
|
255
258
|
},
|
|
256
259
|
} : null);
|
|
257
260
|
|
|
261
|
+
// `fn FUT natgas` with no contract to list fails with what to type instead, not the board's empty labels.
|
|
262
|
+
const noMatch = !!dataProvider && !!searchQuery.trim() && !rows.some((row) => row.type === "row");
|
|
263
|
+
useRemoteUiNode(noMatch ? {
|
|
264
|
+
role: "report-failure",
|
|
265
|
+
label: "No matching contracts",
|
|
266
|
+
getMetadata: () => futuresNoMatchFailure(searchQuery),
|
|
267
|
+
} : null);
|
|
268
|
+
|
|
258
269
|
const status = quoteBoardStatus(listedQuotes);
|
|
259
270
|
const errorMessage = boardErrorMessage(listedQuotes);
|
|
260
271
|
// Every load came back empty: a board of dashes reads as broken, so the
|
|
261
272
|
// body says so once and offers a retry. One quote is enough to keep rows.
|
|
262
273
|
const noQuotes = !!dataProvider && listedQuotes.size > 0 && status.unavailable === listedQuotes.size;
|
|
274
|
+
// Signed out or free, the contracts' quotes are delayed ones.
|
|
275
|
+
const quoteDelay = useQuoteDelayFooter(boardQuotes(listedQuotes), "futures-delay");
|
|
263
276
|
usePaneFooter(FUTURES_PANE_ID, () => {
|
|
264
277
|
if (noQuotes) return { info: [], hints: [] };
|
|
265
278
|
const info: PaneFooterSegment[] = quoteBoardFooterInfo(status);
|
|
266
279
|
if (errorMessage) info.push({ id: "reason", parts: [{ text: errorMessage, tone: "warning" }] });
|
|
280
|
+
if (quoteDelay) info.push(quoteDelay);
|
|
267
281
|
return {
|
|
268
282
|
info,
|
|
269
283
|
hints: [{ id: "search", key: "/", label: "search", onPress: focusSearch }],
|
|
@@ -272,6 +286,7 @@ export function FuturesPane({ focused, width, height }: PaneProps) {
|
|
|
272
286
|
errorMessage,
|
|
273
287
|
focusSearch,
|
|
274
288
|
noQuotes,
|
|
289
|
+
quoteDelay,
|
|
275
290
|
status.latestTs,
|
|
276
291
|
status.loading,
|
|
277
292
|
status.savedAt,
|
|
@@ -149,9 +149,23 @@ function matchesFuturesSearch(contract: FuturesContract, query: string, quote?:
|
|
|
149
149
|
|| contract.name.toLowerCase().includes(normalized)
|
|
150
150
|
|| futuresContractName(contract, quote).toLowerCase().includes(normalized)
|
|
151
151
|
|| contract.symbol.toLowerCase().includes(normalized)
|
|
152
|
+
// Everyday names the board's own does not hold: natgas is Natural Gas.
|
|
153
|
+
|| (contract.aliases ?? []).some((alias) => alias.includes(normalized))
|
|
152
154
|
);
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Why `fn FUT urea` lists nothing, for a search no contract matches: what
|
|
159
|
+
* to type instead, never the board's own buttons ("Clear search.").
|
|
160
|
+
*/
|
|
161
|
+
export function futuresNoMatchFailure(query: string): { message: string; details: string } {
|
|
162
|
+
return {
|
|
163
|
+
message: `No futures contract matches "${query.trim()}".`,
|
|
164
|
+
details: "Try a code such as NG, CL or GC, or a name such as natural gas or gold: gloom fn FUT NG."
|
|
165
|
+
+ " Other boards: gloom fn FUT --tab inventories, positioning or crops.",
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
155
169
|
export const FUTURES_RETURN_COLUMNS: Readonly<Partial<Record<FuturesColumnId, FuturesReturnHorizon>>> = {
|
|
156
170
|
return1w: "1W",
|
|
157
171
|
return1m: "1M",
|