@gloom-sh/terminal 0.19.1 → 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 +2 -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 +58 -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/runtime/startup.ts +3 -1
- package/src/app.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +2 -1
- package/src/capabilities/chart-series.ts +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 +101 -27
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +14 -10
- package/src/cli/commands/search.ts +84 -25
- 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 +6 -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/glossary.ts +2 -1
- package/src/cli/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +189 -11
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +51 -5
- package/src/cli/listing-arg.ts +63 -10
- 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 +49 -6
- 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 +51 -6
- package/src/components/command-bar/selection-runtime.ts +12 -29
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/header.tsx +10 -5
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +8 -2
- package/src/components/layout/pane/footer/model.ts +73 -6
- package/src/components/layout/pane/header.tsx +2 -2
- package/src/components/layout/pane/status-footer.ts +2 -1
- 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 +4 -2
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +60 -26
- 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/display.tsx +16 -5
- package/src/components/ui/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +16 -3
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/core/app-services.ts +11 -2
- package/src/data/app-persistence.ts +9 -7
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/plugin-cache.ts +15 -0
- package/src/data/plugin-state-store.ts +81 -24
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +165 -90
- package/src/data/session-store.ts +31 -8
- package/src/data/sqlite/access.ts +324 -0
- package/src/data/sqlite/checkpointer.ts +285 -0
- package/src/data/sqlite/database.ts +80 -4
- package/src/data/sqlite/retry.ts +50 -9
- package/src/data/ticker-store.ts +57 -23
- 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/market-data/realized-volatility.ts +4 -2
- 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/auto-update-node.ts +10 -6
- 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/central-bank-rates/series.ts +2 -3
- package/src/plugins/builtin/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
- package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
- 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 +30 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
- package/src/plugins/builtin/chart-composer/session-range.ts +5 -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/client.ts +4 -1
- 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/client.ts +28 -2
- package/src/plugins/builtin/cot/headless.ts +4 -1
- package/src/plugins/builtin/cot/index.tsx +5 -4
- package/src/plugins/builtin/cot/model.ts +58 -7
- package/src/plugins/builtin/cot/pane.tsx +6 -2
- package/src/plugins/builtin/cpi/client.ts +6 -5
- 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/series.ts +3 -4
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/credit-conditions/client.ts +22 -14
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- package/src/plugins/builtin/crypto-board/client.ts +5 -5
- 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 +6 -4
- 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/debt-maturities/series.ts +3 -3
- 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/client.ts +6 -5
- package/src/plugins/builtin/doe/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/doe/prices.ts +2 -3
- package/src/plugins/builtin/doe/series.ts +4 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
- package/src/plugins/builtin/earnings-calls/data.ts +19 -11
- package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
- 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/detail-view.tsx +16 -2
- 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/executives/data.ts +6 -1
- package/src/plugins/builtin/executives/pane.tsx +9 -21
- package/src/plugins/builtin/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/cache.ts +4 -0
- 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/fear-greed/series.ts +6 -4
- 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 +19 -3
- 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/gpu/series.ts +2 -3
- package/src/plugins/builtin/help/index.tsx +1 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
- 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/client.ts +4 -1
- 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/client.ts +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +16 -5
- package/src/plugins/builtin/market-movers/screener.ts +23 -6
- package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
- 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/money-markets/series.ts +3 -5
- 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/gas-client.ts +2 -1
- package/src/plugins/builtin/power/index.tsx +1 -2
- package/src/plugins/builtin/power/prices-client.ts +3 -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/risk-factors/data.ts +8 -4
- package/src/plugins/builtin/risk-factors/index.tsx +3 -1
- package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
- 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/scanner/lock-note.ts +26 -3
- 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 +73 -8
- 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/fred-points.ts +6 -1
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- package/src/plugins/builtin/shared/series-warning.ts +13 -4
- 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/short-volume/series.ts +2 -3
- package/src/plugins/builtin/social-mentions/client.ts +15 -10
- package/src/plugins/builtin/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- package/src/plugins/builtin/social-mentions/series.ts +2 -3
- 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 +127 -29
- package/src/plugins/builtin/thirteenf/data.ts +165 -35
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
- package/src/plugins/builtin/thirteenf/types.ts +14 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
- package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
- 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/cache.ts +20 -8
- package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
- package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
- package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
- 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/client.ts +43 -16
- package/src/plugins/builtin/volatility/headless.ts +5 -2
- package/src/plugins/builtin/volatility/index.tsx +1 -2
- package/src/plugins/builtin/volatility/model.ts +17 -6
- package/src/plugins/builtin/volatility/pane.tsx +6 -2
- package/src/plugins/builtin/volatility/series.ts +2 -1
- 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/client.ts +4 -6
- 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 +64 -42
- 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 +5 -2
- 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/detached-windows.ts +23 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
- 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/attention.ts +54 -0
- package/src/renderers/electrobun/bun/window/create.ts +6 -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/electrobun/view/ui-host.tsx +9 -2
- 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/resolve.ts +2 -1
- package/src/series/standalone.ts +10 -1
- package/src/series/types.ts +12 -0
- package/src/series/validate.ts +11 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
- 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 +25 -9
- package/src/telemetry/stall-reports.ts +218 -13
- package/src/test-support/outage.ts +8 -0
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name-listing.ts +59 -29
- package/src/tickers/company-name.ts +84 -1
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +104 -8
- package/src/tickers/search/index.ts +5 -2
- 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/market.ts +5 -3
- package/src/time-series/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +75 -8
- package/src/time-series/resolve.ts +32 -4
- 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/time-series/types.ts +7 -0
- package/src/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +40 -3
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +17 -1
- 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/financial-statements.ts +73 -16
- package/src/utils/fundamentals.ts +17 -5
- package/src/utils/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/lease-liabilities.ts +54 -0
- package/src/utils/main-thread-stall.ts +122 -5
- package/src/utils/perf-marks.ts +58 -12
- package/src/utils/price-history-integrity.ts +47 -1
- 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/time-series/studies.ts +0 -1628
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import type {
|
|
3
|
-
CloudEarningsCallPayload,
|
|
4
|
-
CloudEarningsTranscriptPayload,
|
|
5
|
-
} from "../../../api-client";
|
|
2
|
+
import type { CloudEarningsCallPayload } from "../../../api-client";
|
|
6
3
|
import { recordResearchActivity } from "../../../api-client/research-activity";
|
|
7
4
|
import {
|
|
8
5
|
DataTableStackView,
|
|
@@ -49,7 +46,11 @@ import {
|
|
|
49
46
|
loadEarningsCalls,
|
|
50
47
|
loadTranscript,
|
|
51
48
|
statusOf,
|
|
49
|
+
type EarningsTranscriptResult,
|
|
52
50
|
} from "./data";
|
|
51
|
+
import { isPermanentClientError } from "../../../api-client/errors";
|
|
52
|
+
import { savedCopyFooterInfo } from "../../../components/layout/pane/footer";
|
|
53
|
+
import { STALE_SEGMENT } from "../../../components/layout/pane/status-footer";
|
|
53
54
|
import { callPeriod, callTitle, eventLabelOf, formatCallDate, formatDuration, formatSentiment } from "./format";
|
|
54
55
|
import { findCallForQuarter, findInLines, MIN_FIND_LENGTH, turnParagraphs } from "./model";
|
|
55
56
|
import { READER_TABS, TranscriptView, type ReaderTab, type TranscriptPlayback } from "./transcript-view";
|
|
@@ -148,6 +149,8 @@ interface TickerLookup {
|
|
|
148
149
|
calls: CloudEarningsCallPayload[];
|
|
149
150
|
fetchedAt?: number;
|
|
150
151
|
stale?: boolean;
|
|
152
|
+
/** Its refresh failed and the copy saved at this time (ms) answered instead. */
|
|
153
|
+
savedAt?: number;
|
|
151
154
|
refreshError?: string;
|
|
152
155
|
refreshRequest?: number;
|
|
153
156
|
}
|
|
@@ -280,6 +283,8 @@ function CallsListView({
|
|
|
280
283
|
const [stale, setStale] = useState(initialShelf?.stale === true);
|
|
281
284
|
// The company's home line the shelf is read from, for a receipt or a foreign line.
|
|
282
285
|
const [companyFrom, setCompanyFrom] = useState(initialShelf?.companyFrom ?? null);
|
|
286
|
+
// The list's refresh failed and a copy saved at this time (ms) answered instead.
|
|
287
|
+
const [listSavedAt, setListSavedAt] = useState<number | null>(null);
|
|
283
288
|
const [listFetchedAt, setListFetchedAt] = useState(initialShelf?.fetchedAt ?? 0);
|
|
284
289
|
const listRequestVersion = useRef(0);
|
|
285
290
|
const [listCompletedRequest, setListCompletedRequest] = useState(0);
|
|
@@ -292,7 +297,7 @@ function CallsListView({
|
|
|
292
297
|
|
|
293
298
|
const [selectedId, setSelectedId] = usePluginPaneState<string | null>(`${stateKey}selectedId`, null);
|
|
294
299
|
const [detailOpen, setDetailOpen] = usePluginPaneState<boolean>(`${stateKey}detailOpen`, false);
|
|
295
|
-
const [transcript, setTranscript] = useState<
|
|
300
|
+
const [transcript, setTranscript] = useState<EarningsTranscriptResult | null>(null);
|
|
296
301
|
useEffect(() => {
|
|
297
302
|
if (focused && transcript && detailOpen) recordResearchActivity("pro_feature_used", "transcripts");
|
|
298
303
|
}, [focused, transcript, detailOpen]);
|
|
@@ -344,10 +349,12 @@ function CallsListView({
|
|
|
344
349
|
setHasMore(result.sourceLimitReached === true);
|
|
345
350
|
setStale(result.stale);
|
|
346
351
|
setCompanyFrom(result.companyFrom ?? null);
|
|
352
|
+
setListSavedAt(result.savedAt ?? null);
|
|
347
353
|
setListFetchedAt(result.fetchedAt);
|
|
348
354
|
setListPending(result.pending === true && result.calls.length === 0);
|
|
355
|
+
// A saved copy says when it was saved, in the footer, rather than why the refresh failed.
|
|
349
356
|
setListError(
|
|
350
|
-
result.refreshError ? { message: result.refreshError, status: result.errorStatus } : null,
|
|
357
|
+
result.refreshError && result.savedAt == null ? { message: result.refreshError, status: result.errorStatus } : null,
|
|
351
358
|
);
|
|
352
359
|
setListStatus("loaded");
|
|
353
360
|
setListCompletedRequest(request);
|
|
@@ -357,6 +364,7 @@ function CallsListView({
|
|
|
357
364
|
setListPending(false);
|
|
358
365
|
setHasMore(false);
|
|
359
366
|
setNextOffset(0);
|
|
367
|
+
setListSavedAt(null);
|
|
360
368
|
setListError({
|
|
361
369
|
message: error instanceof Error ? error.message : String(error),
|
|
362
370
|
status: statusOf(error),
|
|
@@ -415,6 +423,7 @@ function CallsListView({
|
|
|
415
423
|
setListPending(held?.pending === true && held.calls.length === 0);
|
|
416
424
|
setStale(held?.stale === true);
|
|
417
425
|
setCompanyFrom(held?.companyFrom ?? null);
|
|
426
|
+
setListSavedAt(null);
|
|
418
427
|
setListFetchedAt(held?.fetchedAt ?? 0);
|
|
419
428
|
setListError(null);
|
|
420
429
|
setLookup(null);
|
|
@@ -482,19 +491,22 @@ function CallsListView({
|
|
|
482
491
|
calls: result.calls,
|
|
483
492
|
fetchedAt: result.fetchedAt,
|
|
484
493
|
stale: result.stale,
|
|
485
|
-
|
|
494
|
+
savedAt: result.savedAt,
|
|
495
|
+
refreshError: result.savedAt == null ? result.refreshError : undefined,
|
|
486
496
|
refreshRequest,
|
|
487
497
|
});
|
|
488
498
|
})
|
|
489
499
|
.catch((error: unknown) => {
|
|
490
500
|
if (cancelled) return;
|
|
491
|
-
|
|
501
|
+
// A refusal asking again will not change clears the rows; a passing failure keeps them.
|
|
502
|
+
const retainRows = !isPermanentClientError(error);
|
|
492
503
|
setLookup((current) => ({
|
|
493
504
|
ticker: lookupTicker,
|
|
494
505
|
status: "error",
|
|
495
506
|
calls: retainRows && current?.ticker === lookupTicker ? current.calls : [],
|
|
496
507
|
fetchedAt: retainRows && current?.ticker === lookupTicker ? current.fetchedAt : undefined,
|
|
497
508
|
stale: retainRows && current?.ticker === lookupTicker && current.calls.length > 0,
|
|
509
|
+
savedAt: retainRows && current?.ticker === lookupTicker ? current.savedAt : undefined,
|
|
498
510
|
refreshError: error instanceof Error ? error.message : String(error),
|
|
499
511
|
refreshRequest,
|
|
500
512
|
}));
|
|
@@ -864,15 +876,14 @@ function CallsListView({
|
|
|
864
876
|
}
|
|
865
877
|
}
|
|
866
878
|
const visibleLookup = lookup && (lookup.ticker === lookupTicker || (detailOpen && selected?.ticker === lookup.ticker)) ? lookup : null;
|
|
867
|
-
if (visibleLookup?.stale) {
|
|
868
|
-
info.push({ id: "lookup-stale", parts: [{ text: `${visibleLookup.ticker} stale cache`, tone: "warning" }] });
|
|
869
|
-
}
|
|
870
879
|
if (visibleLookup?.refreshError && lookupState !== "error") {
|
|
871
880
|
info.push({ id: "lookup-error", parts: [{ text: `${visibleLookup.ticker}: ${visibleLookup.refreshError}`, tone: "warning" }] });
|
|
872
881
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
882
|
+
// What a failed refresh left on screen: a saved copy says when it was saved; a list
|
|
883
|
+
// held from an earlier session, still refreshing, is only stale.
|
|
884
|
+
const savedCopy = savedCopyFooterInfo([listSavedAt, visibleLookup?.savedAt, readerOpen ? selectedTranscript?.savedAt : null]);
|
|
885
|
+
info.push(...savedCopy);
|
|
886
|
+
if (stale && listSavedAt == null) info.push(STALE_SEGMENT);
|
|
876
887
|
if (listError) {
|
|
877
888
|
info.push({ id: "error", parts: [{ text: listError.message, tone: "warning" }] });
|
|
878
889
|
}
|
|
@@ -911,6 +922,8 @@ function CallsListView({
|
|
|
911
922
|
lookupTicker,
|
|
912
923
|
lookup,
|
|
913
924
|
stale,
|
|
925
|
+
listSavedAt,
|
|
926
|
+
selectedTranscript,
|
|
914
927
|
listError,
|
|
915
928
|
proRequired,
|
|
916
929
|
transcriptProRequired,
|
|
@@ -4,8 +4,8 @@ import { DEFAULT_GRAPH_OPTIONS, type GraphOptions, type GraphPayload } from "../
|
|
|
4
4
|
import { errorMessage } from "../../../utils/errors";
|
|
5
5
|
import { addDays, newYorkToday } from "../earnings/board-model";
|
|
6
6
|
import { loadEarningsBoard } from "../earnings/client";
|
|
7
|
-
import { loadSupplyChain } from "../supply-chain/client";
|
|
8
|
-
import { cachedGraph, loadGraph } from "../supply-chain/graph-client";
|
|
7
|
+
import { loadSupplyChain, loadSupplyChainsAhead } from "../supply-chain/client";
|
|
8
|
+
import { cachedGraph, loadGraph, loadGraphsAhead } from "../supply-chain/graph-client";
|
|
9
9
|
import { projectRipple, projectSecondHop, rippleCompanyTickers, RIPPLE_DAYS, secondHopLinks, type RippleRow, type SecondHopRow } from "./model";
|
|
10
10
|
|
|
11
11
|
export interface RippleSources {
|
|
@@ -13,6 +13,10 @@ export interface RippleSources {
|
|
|
13
13
|
calendar(query: EarningsCalendarQuery): Promise<EarningsCalendarPayload>;
|
|
14
14
|
/** SPLC's graph, which returns both hops around a holding in one call. Absent when the account sees one hop only. */
|
|
15
15
|
graph?(symbol: string, options: GraphOptions): Promise<GraphPayload>;
|
|
16
|
+
/** Reads every holding's disclosures ahead in batch requests, for `supplyChain` to find. */
|
|
17
|
+
supplyChainsAhead?(symbols: readonly string[]): Promise<void>;
|
|
18
|
+
/** Reads these holdings' graphs ahead in batch requests, for `graph` to find. */
|
|
19
|
+
graphsAhead?(symbols: readonly string[], options: GraphOptions): Promise<void>;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
/**
|
|
@@ -60,14 +64,29 @@ export interface RippleSnapshot {
|
|
|
60
64
|
*/
|
|
61
65
|
export function cachedRippleSources(accessKey: string, force = false, graph: "off" | "load" | "cached" = "off"): RippleSources & { staleFlags: boolean[] } {
|
|
62
66
|
const staleFlags: boolean[] = [];
|
|
67
|
+
// A forced read takes what this refresh read ahead as fresh.
|
|
68
|
+
const chainsAhead = new Set<string>();
|
|
69
|
+
const graphsAhead = new Set<string>();
|
|
63
70
|
return {
|
|
64
71
|
staleFlags,
|
|
65
|
-
|
|
72
|
+
supplyChainsAhead: async (symbols) => {
|
|
73
|
+
for (const symbol of await loadSupplyChainsAhead(symbols, accessKey, force)) chainsAhead.add(symbol);
|
|
74
|
+
},
|
|
75
|
+
supplyChain: async (symbol) => {
|
|
76
|
+
const result = await loadSupplyChain(symbol, accessKey, force && !chainsAhead.has(symbol.toUpperCase()));
|
|
77
|
+
staleFlags.push(result.stale);
|
|
78
|
+
return result.payload;
|
|
79
|
+
},
|
|
66
80
|
calendar: async (query) => { const result = await loadEarningsBoard(query, force); staleFlags.push(result.stale); return result.payload; },
|
|
67
81
|
// Cached by the full query, apart from SPLC Graph's default one.
|
|
68
82
|
...(graph !== "off" ? { graph: async (symbol: string, options: GraphOptions) => {
|
|
69
83
|
const cached = graph === "cached" ? cachedGraph(symbol, "", options, accessKey) : null;
|
|
70
|
-
const result = cached ?? await loadGraph(symbol, "", options, accessKey, force
|
|
84
|
+
const result = cached ?? await loadGraph(symbol, "", options, accessKey, force && !graphsAhead.has(symbol.toUpperCase()));
|
|
85
|
+
staleFlags.push(result.stale);
|
|
86
|
+
return result.payload;
|
|
87
|
+
} } : {}),
|
|
88
|
+
...(graph === "load" ? { graphsAhead: async (symbols: readonly string[], options: GraphOptions) => {
|
|
89
|
+
for (const symbol of await loadGraphsAhead(symbols, options, accessKey, force)) graphsAhead.add(symbol);
|
|
71
90
|
} } : {}),
|
|
72
91
|
};
|
|
73
92
|
}
|
|
@@ -84,8 +103,12 @@ async function eachLimited(symbols: readonly string[], load: (symbol: string) =>
|
|
|
84
103
|
const locked = () => ({ graphs: new Map<string, GraphPayload>(), failures: [] as Failure[], locked: true });
|
|
85
104
|
const isPreview = (graphs: ReadonlyMap<string, GraphPayload>) => [...graphs.values()].some((graph) => graph.access === "preview");
|
|
86
105
|
|
|
87
|
-
/**
|
|
88
|
-
|
|
106
|
+
/**
|
|
107
|
+
* One graph per holding. The first answer says whether the account sees two
|
|
108
|
+
* hops; a one-hop preview skips the rest, which are otherwise read ahead in
|
|
109
|
+
* batch requests.
|
|
110
|
+
*/
|
|
111
|
+
async function loadGraphs(holdings: readonly string[], graph: NonNullable<RippleSources["graph"]>, ahead?: RippleSources["graphsAhead"]) {
|
|
89
112
|
const graphs = new Map<string, GraphPayload>();
|
|
90
113
|
const failures: Failure[] = [];
|
|
91
114
|
const load = async (symbol: string) => {
|
|
@@ -95,6 +118,7 @@ async function loadGraphs(holdings: readonly string[], graph: NonNullable<Ripple
|
|
|
95
118
|
const [first, ...rest] = holdings;
|
|
96
119
|
if (first) await load(first);
|
|
97
120
|
if (isPreview(graphs)) return locked();
|
|
121
|
+
await ahead?.(rest, RIPPLE_GRAPH_OPTIONS);
|
|
98
122
|
await eachLimited(rest, load);
|
|
99
123
|
// Only when the first holding failed can a preview get this far.
|
|
100
124
|
if (isPreview(graphs)) return locked();
|
|
@@ -107,11 +131,12 @@ export async function loadRipple(holdings: readonly string[], sources: RippleSou
|
|
|
107
131
|
const symbols = [...new Set(holdings.map((symbol) => symbol.toUpperCase()))];
|
|
108
132
|
const chains = new Map<string, SupplyChainPayload>();
|
|
109
133
|
const failures: Failure[] = [];
|
|
134
|
+
await sources.supplyChainsAhead?.(symbols);
|
|
110
135
|
await eachLimited(symbols, async (symbol) => {
|
|
111
136
|
try { chains.set(symbol, await sources.supplyChain(symbol)); }
|
|
112
137
|
catch (error) { failures.push({ symbol, error: errorMessage(error) }); }
|
|
113
138
|
});
|
|
114
|
-
const second = !secondHop ? null : sources.graph ? await loadGraphs(symbols, sources.graph) : locked();
|
|
139
|
+
const second = !secondHop ? null : sources.graph ? await loadGraphs(symbols, sources.graph, sources.graphsAhead) : locked();
|
|
115
140
|
const graphs = second?.graphs ?? new Map<string, GraphPayload>();
|
|
116
141
|
const companies = [...rippleCompanyTickers(chains), ...secondHopLinks(chains, graphs).map((link) => link.company.ticker!.toUpperCase())];
|
|
117
142
|
const reports: EarningsCalendarPayload["reports"] = [];
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { PluginModule } from "../plugin-module";
|
|
2
2
|
import { formatTickerListInput, parseTickerListInput } from "../../../tickers/list";
|
|
3
3
|
import { earningsRippleHeadless } from "./headless";
|
|
4
|
-
import { EarningsRipplePane } from "./pane";
|
|
5
4
|
|
|
6
5
|
function namedSymbols(arg: string | undefined): string[] {
|
|
7
6
|
try { return arg?.trim() ? parseTickerListInput(arg) : []; } catch { return []; }
|
|
@@ -9,7 +8,7 @@ function namedSymbols(arg: string | undefined): string[] {
|
|
|
9
8
|
|
|
10
9
|
export const earningsRippleModule: PluginModule = {
|
|
11
10
|
panes: [{
|
|
12
|
-
id: "earnings-ripple", name: "Earnings Ripple", icon: "E", component: EarningsRipplePane,
|
|
11
|
+
id: "earnings-ripple", name: "Earnings Ripple", icon: "E", get component(): typeof import("./pane").EarningsRipplePane { const { EarningsRipplePane } = require("./pane"); return EarningsRipplePane; },
|
|
13
12
|
defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 96, height: 20 },
|
|
14
13
|
tableExport: true,
|
|
15
14
|
// Without named tickers it lists what is in the person's portfolios.
|
|
@@ -256,17 +256,40 @@ export function econEventName(event: Pick<EconEvent, "country" | "event" | "actu
|
|
|
256
256
|
/** Letters and digits only, lower case: "Non-Farm" and "nonfarm" read alike. */
|
|
257
257
|
const compactName = (text: string) => text.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
258
258
|
|
|
259
|
+
/** Words an initialism leaves out: the U.S. Department of Agriculture is USDA. */
|
|
260
|
+
const INITIALISM_SKIPS = new Set(["of", "the", "and", "for", "on", "in"]);
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* The ways a row's publisher is written, compacted: its name, its initials
|
|
264
|
+
* ("usda" for the U.S. Department of Agriculture, "bls" for the Bureau of
|
|
265
|
+
* Labor Statistics) and the name of its site (usda.gov).
|
|
266
|
+
*/
|
|
267
|
+
function publisherNames(source: EconEvent["source"]): string[] {
|
|
268
|
+
if (!source?.name?.trim()) return [];
|
|
269
|
+
const initials = source.name.split(/[\s.,&/()-]+/).filter((word) => word && !INITIALISM_SKIPS.has(word.toLowerCase()))
|
|
270
|
+
.map((word) => word[0]!).join("");
|
|
271
|
+
let site = "";
|
|
272
|
+
try {
|
|
273
|
+
site = source.url ? new URL(source.url).hostname.replace(/^www\./i, "").split(".")[0] ?? "" : "";
|
|
274
|
+
} catch {
|
|
275
|
+
// Intentional ignore: a publisher's address that does not parse leaves its name and initials.
|
|
276
|
+
}
|
|
277
|
+
return [source.name, initials, site].map(compactName).filter(Boolean);
|
|
278
|
+
}
|
|
279
|
+
|
|
259
280
|
/**
|
|
260
|
-
* Whether a row
|
|
261
|
-
* punctuation aside: `nonfarm` finds Non Farm Payrolls and
|
|
262
|
-
* Employment Change, `fed funds` the Federal Funds Rate
|
|
263
|
-
*
|
|
281
|
+
* Whether a row carries every word of `query` in its name or its publisher's,
|
|
282
|
+
* case, spaces and punctuation aside: `nonfarm` finds Non Farm Payrolls and
|
|
283
|
+
* Non-Farm Employment Change, `fed funds` the Federal Funds Rate, and `usda`
|
|
284
|
+
* every row the U.S. Department of Agriculture publishes. The name shown and
|
|
285
|
+
* the feed's own are both read.
|
|
264
286
|
*/
|
|
265
|
-
export function matchesEventSearch(event: Pick<EconEvent, "country" | "event" | "actual" | "forecast" | "prior">, query: string): boolean {
|
|
287
|
+
export function matchesEventSearch(event: Pick<EconEvent, "country" | "event" | "actual" | "forecast" | "prior" | "source">, query: string): boolean {
|
|
266
288
|
const words = query.split(/\s+/).map(compactName).filter(Boolean);
|
|
267
289
|
if (words.length === 0) return true;
|
|
268
290
|
const names = [event.event, econEventName(event)].map(compactName);
|
|
269
|
-
|
|
291
|
+
const publisher = publisherNames(event.source);
|
|
292
|
+
return names.some((name) => words.every((word) => name.includes(word) || publisher.some((text) => text.includes(word))));
|
|
270
293
|
}
|
|
271
294
|
|
|
272
295
|
/**
|
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
type StatItem,
|
|
19
19
|
} from "../../../components";
|
|
20
20
|
import { spanDigits } from "../../../components/chart-table";
|
|
21
|
+
import { savedCopyFooterInfo } from "../../../components/layout/pane/footer";
|
|
22
|
+
import { isPermanentClientError } from "../../../api-client/errors";
|
|
21
23
|
import { getTableWidth, hasMeaningfulTableHorizontalOverflow } from "../../../components/ui/table-layout";
|
|
22
24
|
import type { CompositeAxisDomain } from "../../../components/chart/composite/types";
|
|
23
25
|
import {
|
|
@@ -123,6 +125,8 @@ export function EconDetailView({ event, width, height, focused }: EconDetailView
|
|
|
123
125
|
const [loading, setLoading] = useState(false);
|
|
124
126
|
const [error, setError] = useState<string | null>(null);
|
|
125
127
|
const [freshnessWarning, setFreshnessWarning] = useState<string | null>(null);
|
|
128
|
+
// The history's refresh failed and the copy saved at this time (ms) answered instead.
|
|
129
|
+
const [savedAt, setSavedAt] = useState<number | null>(null);
|
|
126
130
|
const [data, setData] = useState<FredSeriesData | null>(null);
|
|
127
131
|
// Null follows the latest period.
|
|
128
132
|
const [selectedDate, setSelectedDate] = useState<string | null>(null);
|
|
@@ -146,6 +150,7 @@ export function EconDetailView({ event, width, height, focused }: EconDetailView
|
|
|
146
150
|
setLoading(false);
|
|
147
151
|
setError(null);
|
|
148
152
|
setFreshnessWarning(null);
|
|
153
|
+
setSavedAt(null);
|
|
149
154
|
return;
|
|
150
155
|
}
|
|
151
156
|
|
|
@@ -156,6 +161,7 @@ export function EconDetailView({ event, width, height, focused }: EconDetailView
|
|
|
156
161
|
setLoading(false);
|
|
157
162
|
setError(null);
|
|
158
163
|
setFreshnessWarning(null);
|
|
164
|
+
setSavedAt(null);
|
|
159
165
|
return;
|
|
160
166
|
}
|
|
161
167
|
} else {
|
|
@@ -165,6 +171,7 @@ export function EconDetailView({ event, width, height, focused }: EconDetailView
|
|
|
165
171
|
setLoading(true);
|
|
166
172
|
setError(null);
|
|
167
173
|
setFreshnessWarning(null);
|
|
174
|
+
setSavedAt(null);
|
|
168
175
|
|
|
169
176
|
loadCachedFredSeries(
|
|
170
177
|
request,
|
|
@@ -174,12 +181,18 @@ export function EconDetailView({ event, width, height, focused }: EconDetailView
|
|
|
174
181
|
}),
|
|
175
182
|
)
|
|
176
183
|
.then((entry) => {
|
|
184
|
+
// A refusal or a reply that fails validation is thrown by the load, never covered by a saved copy.
|
|
185
|
+
const saved = entry.source === "stale-fallback";
|
|
177
186
|
setData(entry.data);
|
|
178
|
-
|
|
179
|
-
|
|
187
|
+
// A copy a failed refresh left says when it was saved, in the footer as every pane does;
|
|
188
|
+
// a series its source has not updated since is a note.
|
|
189
|
+
setSavedAt(saved ? entry.fetchedAt : null);
|
|
190
|
+
setFreshnessWarning(entry.stale && !saved
|
|
191
|
+
? `Source not updated since ${new Date(entry.fetchedAt).toISOString().slice(0, 10)}.`
|
|
180
192
|
: null);
|
|
181
193
|
})
|
|
182
194
|
.catch((err) => {
|
|
195
|
+
if (isPermanentClientError(err)) setData(null);
|
|
183
196
|
setError(err instanceof Error ? err.message : String(err));
|
|
184
197
|
})
|
|
185
198
|
.finally(() => {
|
|
@@ -199,6 +212,7 @@ export function EconDetailView({ event, width, height, focused }: EconDetailView
|
|
|
199
212
|
// which asks which one when there are several.
|
|
200
213
|
const openTickerChoice = useOpenTickerChoice();
|
|
201
214
|
const relatedTickers = useMemo(() => mapping?.relatedTickers ?? [], [mapping]);
|
|
215
|
+
usePaneFooter("econ-detail:saved", () => ({ info: savedCopyFooterInfo([savedAt]) }), [savedAt]);
|
|
202
216
|
usePaneFooter("econ-detail:related", () => relatedTickers.length > 0 ? {
|
|
203
217
|
hints: [{
|
|
204
218
|
id: "related",
|
|
@@ -2,7 +2,6 @@ import type { PluginModule } from "../plugin-module";
|
|
|
2
2
|
import { statsCache } from "./cache";
|
|
3
3
|
import { STAT_CATEGORIES } from "./defs";
|
|
4
4
|
import { econStatisticsHeadless } from "./headless";
|
|
5
|
-
import { EconStatisticsPane } from "./pane";
|
|
6
5
|
import { resolveStatArg, STATS, DEFAULT_STAT_ID } from "./stats";
|
|
7
6
|
|
|
8
7
|
|
|
@@ -13,7 +12,7 @@ export const econStatisticsModule: PluginModule = {
|
|
|
13
12
|
id: ECON_STATISTICS_PANE_ID,
|
|
14
13
|
name: "Economic Statistics",
|
|
15
14
|
icon: "E",
|
|
16
|
-
component: EconStatisticsPane,
|
|
15
|
+
get component(): typeof import("./pane").EconStatisticsPane { const { EconStatisticsPane } = require("./pane"); return EconStatisticsPane; },
|
|
17
16
|
defaultPosition: "right",
|
|
18
17
|
defaultMode: "floating",
|
|
19
18
|
defaultFloatingSize: { width: 120, height: 36 },
|
|
@@ -21,7 +21,7 @@ const savedPath = (id: string) => `saved/${encodeURIComponent(id)}`;
|
|
|
21
21
|
export const screenerApi = {
|
|
22
22
|
query: (query: ScreenQuery, signal?: AbortSignal, client: ScreenerClient = apiClient) =>
|
|
23
23
|
client.equityScreener<ScreenPayload>("query", json("POST", query, { signal })),
|
|
24
|
-
fields: (signal?: AbortSignal) => apiClient.equityScreener<ScreenFieldsResponse>("fields?include=social,research,attention,hiring-apps,kpis,volatility,dividends,technical,option-volatility,ownership", { signal }),
|
|
24
|
+
fields: (signal?: AbortSignal) => apiClient.equityScreener<ScreenFieldsResponse>("fields?include=social,research,attention,hiring-apps,kpis,volatility,dividends,technical,option-volatility,ownership,quality", { signal }),
|
|
25
25
|
export: (definition: ScreenDefinition, snapshotId: string) =>
|
|
26
26
|
apiClient.equityScreener<ScreenExportResponse>("export", json("POST", { ...definition, snapshotId }, { headers: { Accept: "application/json" } })),
|
|
27
27
|
saved: async (signal?: AbortSignal, client: ScreenerClient = apiClient) =>
|
|
@@ -251,6 +251,7 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
251
251
|
"IV percentile is the share of the past 52 weeks' options closes with IV30 below the latest; IV30 changes are in volatility points (25% to 27% is +2). IV fields with no close in five days are unavailable. HV20 is 20-session realized volatility, annualized, and HV rank places it between its 52-week low and high.",
|
|
252
252
|
"Technical fields (RSI, moving averages, MACD and golden crosses, new highs and lows, Bollinger width) read US listings' completed daily sessions: they describe the last close, not the session in progress. A days-since field counts trading days, 0 at the last session.",
|
|
253
253
|
"Shares outstanding, float, short % of float and institutional ownership cover US filers: the 10-K or 10-Q cover count, the stored statistics float, the FINRA settlement, the 13F quarter. The source does not say when it measured the float, so float and short % of float are partial, dated by when the statistics were retrieved (within 30 days) and never above the dated shares outstanding. A stale or inconsistent input leaves a field unavailable with its reason. Institutional ownership is over the balance-sheet count at the 13F quarter end; over 100% is partial with its reason.",
|
|
254
|
+
"ROIC, FCF yield, net debt / EBITDA and debt / equity read FA's latest annual column: the trailing four quarters, or the fiscal year when those quarters are it. Revenue and EPS CAGRs run from the latest fiscal year back 3 or 5 years, both years from the stored statements or both from the SEC filings (US filers), never one of each. Forward growth is the consensus EE shows for the fiscal year in progress. N/M where FA prints it: EBITDA, equity, invested capital or a base year at or below zero.",
|
|
254
255
|
],
|
|
255
256
|
},
|
|
256
257
|
async load(args, ctx) {
|
|
@@ -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"],
|