@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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { ApiRequestError } from "../../../api-client/errors";
|
|
2
|
+
import type { FundBondFacts } from "../../../api-client/fund-facts";
|
|
3
|
+
import { createConcurrencyLimiter } from "../../../utils/concurrency";
|
|
4
|
+
import type { DividendData } from "./client";
|
|
5
|
+
import type { DividendRankingEntry } from "./ranking";
|
|
6
|
+
|
|
7
|
+
/** Tickers one ranking reads at most: `fn DVD`, `shot DVD` and the DVD pane alike. */
|
|
8
|
+
export const MAX_RANKED_SYMBOLS = 25;
|
|
9
|
+
/** Tickers read at once across every ranking in the process, so a ranking never floods Gloom Cloud. */
|
|
10
|
+
const RANKING_CONCURRENCY = 4;
|
|
11
|
+
/** A rate-limited read waits what the server asks, a second when it names nothing, and never longer than this. */
|
|
12
|
+
const RATE_LIMIT_WAIT_MS = 1_000;
|
|
13
|
+
const RATE_LIMIT_WAIT_MAX_MS = 5_000;
|
|
14
|
+
|
|
15
|
+
/** The one gate every ranking's reads pass through. */
|
|
16
|
+
const rankingReads = createConcurrencyLimiter(RANKING_CONCURRENCY);
|
|
17
|
+
|
|
18
|
+
/** How a ranking reads one ticker. */
|
|
19
|
+
export interface DividendRankingReads {
|
|
20
|
+
loadData(symbol: string): Promise<DividendData>;
|
|
21
|
+
/** A fund's bond facts, null for any other listing. It never rejects: without them the fund ranks as before. */
|
|
22
|
+
loadBond?(symbol: string): Promise<FundBondFacts | null>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface RankingReadOptions {
|
|
26
|
+
/** How the one retry of a rate-limited read waits; tests pass their own. */
|
|
27
|
+
sleep?: (ms: number) => Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const wait = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
|
|
31
|
+
|
|
32
|
+
/** The 429 a read was refused with, wrapped or not; null for any other failure. */
|
|
33
|
+
function rateLimited(error: unknown): ApiRequestError | null {
|
|
34
|
+
for (let current: unknown = error, depth = 0; current && typeof current === "object" && depth < 4; depth += 1) {
|
|
35
|
+
if (current instanceof ApiRequestError) return current.status === 429 ? current : null;
|
|
36
|
+
current = (current as { cause?: unknown }).cause;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A read Gloom Cloud refused with 429, asked once more after the wait it names; a second refusal is the row's error. */
|
|
42
|
+
async function retryRateLimitedOnce<T>(read: () => Promise<T>, sleep: (ms: number) => Promise<void>): Promise<T> {
|
|
43
|
+
try {
|
|
44
|
+
return await read();
|
|
45
|
+
} catch (error) {
|
|
46
|
+
const limited = rateLimited(error);
|
|
47
|
+
if (!limited) throw error;
|
|
48
|
+
await sleep(Math.min(Math.max(0, limited.retryAfterMs ?? RATE_LIMIT_WAIT_MS), RATE_LIMIT_WAIT_MAX_MS));
|
|
49
|
+
return read();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Each ticker of a ranking, read into its entry in the order given: the first
|
|
55
|
+
* 25, at most four at once through the gate every ranking shares, so `fn`,
|
|
56
|
+
* `shot` and any open DVD pane together never ask Gloom Cloud for more. A
|
|
57
|
+
* ticker that fails keeps its row with its error, after one more try when it
|
|
58
|
+
* was rate limited.
|
|
59
|
+
*/
|
|
60
|
+
export function readDividendRanking(
|
|
61
|
+
symbols: readonly string[],
|
|
62
|
+
reads: DividendRankingReads,
|
|
63
|
+
{ sleep = wait }: RankingReadOptions = {},
|
|
64
|
+
): Promise<DividendRankingEntry[]> {
|
|
65
|
+
return Promise.all(symbols.slice(0, MAX_RANKED_SYMBOLS).map((symbol) => rankingReads(async (): Promise<DividendRankingEntry> => {
|
|
66
|
+
const bond = reads.loadBond?.(symbol) ?? null;
|
|
67
|
+
try {
|
|
68
|
+
return { symbol, data: await retryRateLimitedOnce(() => reads.loadData(symbol), sleep), bond: await bond };
|
|
69
|
+
} catch (error) {
|
|
70
|
+
return { symbol, error: error instanceof Error ? error.message : String(error) };
|
|
71
|
+
}
|
|
72
|
+
})));
|
|
73
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { useCallback, useMemo, useRef } from "react";
|
|
2
|
+
import { apiClient } from "../../../api-client";
|
|
3
|
+
import {
|
|
4
|
+
DataTableView,
|
|
5
|
+
PaneStatusBody,
|
|
6
|
+
usePaneFooter,
|
|
7
|
+
type DataTableCell,
|
|
8
|
+
type DataTableColumn,
|
|
9
|
+
} from "../../../components";
|
|
10
|
+
import { loadingErrorFooterInfo, usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
11
|
+
import { useAsyncResource } from "../../../react/async-resource";
|
|
12
|
+
import { useAssetData, usePluginAppActions, usePluginPaneState } from "../../../public/react";
|
|
13
|
+
import { useAppSelector } from "../../../state/app/context";
|
|
14
|
+
import { resolveSavedInstrument } from "../../../tickers/saved-instrument";
|
|
15
|
+
import { colors } from "../../../theme/colors";
|
|
16
|
+
import type { HeadlessPaneColumn } from "../../../types/headless";
|
|
17
|
+
import { TextAttributes } from "../../../ui";
|
|
18
|
+
import { compareSortValues, nextHeaderSort, type SortComparableValue, type SortPreference } from "../../../utils/sort-values";
|
|
19
|
+
import { SignInWall } from "../cloud/auth-actions";
|
|
20
|
+
import { isCloudSessionRequired, useResearchCloudSession } from "../shared/research-cloud-session";
|
|
21
|
+
import { fetchDividendData } from "./client";
|
|
22
|
+
import { dividendRankingReads, type DividendReadContext } from "./headless";
|
|
23
|
+
import { projectDividendRanking, type DividendRankingEntry } from "./ranking";
|
|
24
|
+
import { readDividendRanking, type DividendRankingReads } from "./ranking-reads";
|
|
25
|
+
|
|
26
|
+
/** The pane setting a ranking of several tickers is opened with (`shot DVD KO JNJ`). */
|
|
27
|
+
export const RANK_SYMBOLS_SETTING = "rankSymbols";
|
|
28
|
+
|
|
29
|
+
/** The tickers a pane ranks, from its settings: two or more, or none for a pane of one ticker. */
|
|
30
|
+
export function rankSymbolsOf(settings: Readonly<Record<string, unknown>> | undefined): string[] {
|
|
31
|
+
const value = settings?.[RANK_SYMBOLS_SETTING];
|
|
32
|
+
const symbols = Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === "string" && entry.trim() !== "") : [];
|
|
33
|
+
return symbols.length > 1 ? symbols : [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** How wide each of the ranking's columns starts, in characters; a column not named fits its header. */
|
|
37
|
+
const COLUMN_WIDTHS: Record<string, number> = { symbol: 10, lastExDate: 10, payDate: 10, note: 24, yieldBasis: 24 };
|
|
38
|
+
/** Growth figures read by their sign, as the single-ticker pane colors them. */
|
|
39
|
+
const SIGNED_COLUMNS = new Set(["growth1Y", "growth3Y"]);
|
|
40
|
+
const DATE_COLUMNS = new Set(["lastExDate", "payDate", "secYieldAsOf"]);
|
|
41
|
+
|
|
42
|
+
type RankingRow = Record<string, unknown> & { symbol: string };
|
|
43
|
+
type RankingColumn = DataTableColumn & { source: HeadlessPaneColumn };
|
|
44
|
+
/**
|
|
45
|
+
* The order `fn DVD` ranks in: trailing yield, highest first. A stable sort
|
|
46
|
+
* of the ranked rows keeps a ticker not read last among those with no yield.
|
|
47
|
+
*/
|
|
48
|
+
const RANKED_ORDER: SortPreference<string> = { columnId: "trailingYield", direction: "desc" };
|
|
49
|
+
|
|
50
|
+
function sortRankingRows(rows: RankingRow[], sort: SortPreference<string>): RankingRow[] {
|
|
51
|
+
const { columnId, direction } = sort;
|
|
52
|
+
if (!columnId) return rows;
|
|
53
|
+
return [...rows].sort((left, right) => compareSortValues(left[columnId] as SortComparableValue, right[columnId] as SortComparableValue, direction));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Signed out, every read is refused for the account: the pane shows the wall the single-ticker DVD shows. */
|
|
57
|
+
function needsSignIn(entries: readonly DividendRankingEntry[] | null): boolean {
|
|
58
|
+
return !!entries?.length && entries.every((entry) => !("data" in entry))
|
|
59
|
+
&& entries.some((entry) => "error" in entry && isCloudSessionRequired(entry.error));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cellText(column: HeadlessPaneColumn, row: RankingRow): string {
|
|
63
|
+
const value = row[column.key];
|
|
64
|
+
const format = column.textFormat ?? column.format;
|
|
65
|
+
return format ? format(value, row) : value == null ? "" : String(value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Several tickers ranked by trailing yield in one table, with the columns and
|
|
70
|
+
* figures `fn DVD A B C` prints; Enter opens a ticker's own dividend pane.
|
|
71
|
+
*/
|
|
72
|
+
export function DividendRankingView({ symbols, focused, width, height, loadData }: {
|
|
73
|
+
symbols: readonly string[];
|
|
74
|
+
focused: boolean;
|
|
75
|
+
width: number;
|
|
76
|
+
height: number;
|
|
77
|
+
/** One listing's history alone, without the quote `fn DVD` prices it at; for tests. */
|
|
78
|
+
loadData?: typeof fetchDividendData;
|
|
79
|
+
}) {
|
|
80
|
+
const key = symbols.join(",");
|
|
81
|
+
const marketData = useAssetData();
|
|
82
|
+
const cloudSession = useResearchCloudSession();
|
|
83
|
+
// A key reads the listing `fn DVD` reads for it: its own venue, else the one its saved ticker is on.
|
|
84
|
+
// Through a ref, so a quote that updates a saved ticker does not read the ranking again.
|
|
85
|
+
const tickers = useAppSelector((state) => state.tickers);
|
|
86
|
+
const tickersRef = useRef(tickers);
|
|
87
|
+
tickersRef.current = tickers;
|
|
88
|
+
const reads = useMemo<DividendRankingReads>(() => {
|
|
89
|
+
const resolveInstrument = (typed: string) => resolveSavedInstrument(typed, (saved) => tickersRef.current.get(saved));
|
|
90
|
+
// `fn DVD`'s own reads: the listing's quote for the price, the stated forward rate, a fund's bond facts.
|
|
91
|
+
if (marketData && !loadData) return dividendRankingReads({ marketData, apiClient, resolveInstrument } satisfies DividendReadContext);
|
|
92
|
+
const history = loadData ?? fetchDividendData;
|
|
93
|
+
return { loadData: async (typed) => {
|
|
94
|
+
const listing = await resolveInstrument(typed);
|
|
95
|
+
return history(listing.symbol, null, listing.exchange ?? "");
|
|
96
|
+
} };
|
|
97
|
+
}, [loadData, marketData]);
|
|
98
|
+
// A sign-in or a verified email reads the ranking again.
|
|
99
|
+
const request = useCallback(() => readDividendRanking(key.split(","), reads), [key, reads, cloudSession.requestKey]);
|
|
100
|
+
const resource = useAsyncResource(request, { keepPreviousData: true });
|
|
101
|
+
const authWall = needsSignIn(resource.data);
|
|
102
|
+
const { createPaneFromTemplate } = usePluginAppActions();
|
|
103
|
+
const section = useMemo(() => resource.data ? projectDividendRanking(resource.data).sections[0] ?? null : null, [resource.data]);
|
|
104
|
+
const [sort, setSort] = usePluginPaneState<SortPreference<string>>("dividend-ranking:sort", RANKED_ORDER);
|
|
105
|
+
const rows = useMemo(() => sortRankingRows((section?.rows ?? []) as RankingRow[], sort), [section, sort]);
|
|
106
|
+
const columns = useMemo<RankingColumn[]>(() => (section?.columns ?? []).map((column) => ({
|
|
107
|
+
id: column.key,
|
|
108
|
+
label: column.header,
|
|
109
|
+
width: Math.max(column.header.length, COLUMN_WIDTHS[column.key] ?? 0),
|
|
110
|
+
align: column.align === "right" ? "right" : "left",
|
|
111
|
+
source: column,
|
|
112
|
+
})), [section]);
|
|
113
|
+
const [selected, setSelected] = usePluginPaneState<string | null>("dividend-ranking:selected", null);
|
|
114
|
+
|
|
115
|
+
usePaneFooter("dividend-ranking", () => ({
|
|
116
|
+
info: loadingErrorFooterInfo(resource.loading && !!resource.data, resource.data ? null : resource.error),
|
|
117
|
+
}), [resource.loading, resource.data, resource.error]);
|
|
118
|
+
usePaneRefreshKey(resource.reload, { focused, enabled: !authWall });
|
|
119
|
+
|
|
120
|
+
const renderCell = useCallback((row: RankingRow, column: RankingColumn): DataTableCell => {
|
|
121
|
+
const text = cellText(column.source, row);
|
|
122
|
+
const value = row[column.id];
|
|
123
|
+
if (column.id === "symbol") return { text, color: colors.textBright, attributes: TextAttributes.BOLD };
|
|
124
|
+
if (column.id === "note") return { text, color: colors.warning };
|
|
125
|
+
if (DATE_COLUMNS.has(column.id) || column.id === "yieldBasis") return { text, color: colors.textDim };
|
|
126
|
+
if (SIGNED_COLUMNS.has(column.id) && typeof value === "number") {
|
|
127
|
+
return { text, color: value > 0 ? colors.positive : value < 0 ? colors.negative : colors.text };
|
|
128
|
+
}
|
|
129
|
+
return { text };
|
|
130
|
+
}, []);
|
|
131
|
+
|
|
132
|
+
if (authWall) return <SignInWall placement="dividend-yield-signin" action="view dividend history" needsVerification={cloudSession.needsVerification} />;
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<PaneStatusBody
|
|
136
|
+
subject="dividends"
|
|
137
|
+
loading={!resource.data && resource.loading}
|
|
138
|
+
error={!resource.data ? resource.error : null}
|
|
139
|
+
empty={false}
|
|
140
|
+
>
|
|
141
|
+
<DataTableView<RankingRow, RankingColumn>
|
|
142
|
+
focused={focused}
|
|
143
|
+
columns={columns}
|
|
144
|
+
items={rows}
|
|
145
|
+
rootWidth={width}
|
|
146
|
+
rootHeight={height}
|
|
147
|
+
freezeFirstColumn
|
|
148
|
+
sortColumnId={sort.columnId}
|
|
149
|
+
sortDirection={sort.direction}
|
|
150
|
+
// A third click on a column returns to the ranking.
|
|
151
|
+
onHeaderClick={(id) => setSort((current) => nextHeaderSort(current, id, {
|
|
152
|
+
firstDirection: id === "symbol" || id === "note" || id === "yieldBasis" ? "asc" : "desc", resetTo: RANKED_ORDER }))}
|
|
153
|
+
getItemKey={(row) => row.symbol}
|
|
154
|
+
selection={{ kind: "id", selectedId: rows.some((row) => row.symbol === selected) ? selected! : rows[0]?.symbol ?? "",
|
|
155
|
+
getId: (row) => row.symbol, onChange: (id) => setSelected(id) }}
|
|
156
|
+
onActivate={(row) => createPaneFromTemplate("dividend-yield-pane", { symbol: row.symbol })}
|
|
157
|
+
renderCell={renderCell}
|
|
158
|
+
selectedTextOverridesCellColor
|
|
159
|
+
emptyStateTitle="No tickers"
|
|
160
|
+
/>
|
|
161
|
+
</PaneStatusBody>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
@@ -146,7 +146,7 @@ const RANKING_COLUMNS: HeadlessPaneColumn[] = [
|
|
|
146
146
|
{ key: "growth3Y", header: "3Y CAGR", align: "right", format: growth("growth3Y"),
|
|
147
147
|
description: "Compound annual growth of trailing twelve-month cash per share over three years; n/a when either year holds fewer payments than the cadence pays." },
|
|
148
148
|
{ key: "lastExDate", header: "Last ex", format: day, shrink: false },
|
|
149
|
-
{ key: "payDate", header: "
|
|
149
|
+
{ key: "payDate", header: "Next pay", format: day, shrink: false, stackPriority: 2,
|
|
150
150
|
description: "Pay date of the next declared distribution; past distributions carry ex-dates only." },
|
|
151
151
|
];
|
|
152
152
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { DividendPayment, GrowthPayments } from "./types";
|
|
2
|
+
import { formatCurrency } from "../../../utils/format";
|
|
3
|
+
import { currencyMinorDigits } from "../../../market-data/market/format";
|
|
4
|
+
import { resolveCurrencyUnit } from "../../../utils/currency-units";
|
|
2
5
|
import type { ProjectedChartPoint } from "../../../components/chart/core/data";
|
|
3
6
|
import { calendarMonthsBefore } from "../../../utils/calendar-date";
|
|
4
7
|
import { NOT_MEANINGFUL, formatPriceEarnings } from "../../../utils/price-earnings";
|
|
@@ -6,6 +9,24 @@ import { LATE_GRACE_DAYS, trailingCashAt, type TrailingPayments } from "../../..
|
|
|
6
9
|
|
|
7
10
|
const DAY = 24 * 60 * 60 * 1000;
|
|
8
11
|
|
|
12
|
+
/**
|
|
13
|
+
* A cash amount the listing pays as a conversion of the currency its company
|
|
14
|
+
* declares in (`DividendData.convertedFrom`), to two decimals of the unit its
|
|
15
|
+
* source states it in: pence stay pence (BP's 6.4059p is `6.41p`, never
|
|
16
|
+
* `£0.06`), and a major unit keeps its currency's decimals (`ZAR 0.87`, not
|
|
17
|
+
* `ZAR 0.866087`), as the conversion gives it no more.
|
|
18
|
+
*/
|
|
19
|
+
export function formatConvertedAmount(value: number | null | undefined, currency: string, subUnit?: string): string {
|
|
20
|
+
if (value == null || !Number.isFinite(value)) return "—";
|
|
21
|
+
const unit = subUnit ? resolveCurrencyUnit(subUnit) : null;
|
|
22
|
+
return unit && unit.divisor !== 1 && unit.currency === currency
|
|
23
|
+
? formatCurrency(value * unit.divisor, subUnit!, 2)
|
|
24
|
+
: formatCurrency(value, currency, currencyMinorDigits(currency));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** What a converted amount says beside it: `converted from USD`. */
|
|
28
|
+
export const convertedFromText = (currency: string) => `converted from ${currency}`;
|
|
29
|
+
|
|
9
30
|
export function formatDividendYield(value: number | null): string {
|
|
10
31
|
return value != null && Number.isFinite(value) ? `${(value * 100).toFixed(2)}%` : "—";
|
|
11
32
|
}
|
|
@@ -6,7 +6,6 @@ import type { PaneTemplateContext, PaneTemplateCreateOptions } from "../../../ty
|
|
|
6
6
|
import { getSharedRegistry } from "../../registry";
|
|
7
7
|
import { buildRegistryHelpIndex } from "../help/function-index";
|
|
8
8
|
import type { PluginModule } from "../plugin-module";
|
|
9
|
-
import { DocsPane } from "./pane";
|
|
10
9
|
import { docsTopicForArgument, docsTopicForPane } from "./targets";
|
|
11
10
|
|
|
12
11
|
/** One reader: every request lands in the same pane, on top of its history. */
|
|
@@ -38,8 +37,8 @@ export const docsModule: PluginModule = {
|
|
|
38
37
|
id: "docs",
|
|
39
38
|
name: "Docs",
|
|
40
39
|
icon: "?",
|
|
41
|
-
component: DocsPane,
|
|
42
|
-
reportFreshness: { status: "not-a-feed", basis: "documentation" },
|
|
40
|
+
get component(): typeof import("./pane").DocsPane { const { DocsPane } = require("./pane"); return DocsPane; },
|
|
41
|
+
reportFreshness: { status: "not-a-feed", basis: "documentation", local: true },
|
|
43
42
|
// A page reads whole in a report, not cut at the first screen.
|
|
44
43
|
shotFitsReader: true,
|
|
45
44
|
defaultPosition: "right",
|
|
@@ -49,6 +49,14 @@ function seasonalRows(row: DoeSeriesRow) {
|
|
|
49
49
|
fiveYearLow: level(min), fiveYearAverage: level(average), fiveYearHigh: level(max) }));
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/** Words for natural gas storage, which `NGS` reads, rather than a series of the petroleum tables. */
|
|
53
|
+
const GAS_WORDS = new Set(["ngs", "ng", "gas", "natgas", "naturalgas", "natgasstorage", "naturalgasstorage", "gasstorage", "storage", "workinggas"]);
|
|
54
|
+
|
|
55
|
+
/** Whether `DOE` was given a word for gas storage (`NGS`, `natural gas`) rather than a series. */
|
|
56
|
+
export function doeGasWord(argument: string): boolean {
|
|
57
|
+
return GAS_WORDS.has(argument.toLowerCase().replace(/[^a-z]/g, ""));
|
|
58
|
+
}
|
|
59
|
+
|
|
52
60
|
/** `DOE` reads every tab unless one is named; `NGS` starts on gas storage; a series argument adds its year. */
|
|
53
61
|
export function doeHeadless(defaultTab: DoeTab | "all"): HeadlessPaneDefinition<"bundle"> {
|
|
54
62
|
return {
|
|
@@ -77,10 +85,13 @@ export function doeHeadless(defaultTab: DoeTab | "all"): HeadlessPaneDefinition<
|
|
|
77
85
|
nextExpectedAt: oldestReportTime(reports.map((report) => report.nextReleaseAt)) };
|
|
78
86
|
};
|
|
79
87
|
const argument = Array.isArray(args.argument) ? args.argument.join(" ") : args.argument;
|
|
80
|
-
|
|
88
|
+
// `DOE NGS` or `DOE natural gas` is the gas storage tables `NGS` opens on, and says so; `NGS gas` is them already.
|
|
89
|
+
const gasWord = argument ? doeGasWord(argument) : false;
|
|
90
|
+
const ngs = defaultTab === "gas";
|
|
91
|
+
if (argument && !gasWord) {
|
|
81
92
|
const option = doeSeriesOption(argument);
|
|
82
93
|
const row = option ? data.series.find((entry) => entry.id === option.value) : null;
|
|
83
|
-
if (!row) throw new Error(
|
|
94
|
+
if (!row) throw new Error(`Use a series such as cushing, gasoline, distillate or east${ngs ? "" : "; for gas storage, gloom fn NGS"}`);
|
|
84
95
|
return {
|
|
85
96
|
sections: [
|
|
86
97
|
{ title: doeHeaderLine(doeReport(data, row.tab)) ?? row.label, columns: SUMMARY_COLUMNS, rows: [reportRow(row)] },
|
|
@@ -95,8 +106,10 @@ export function doeHeadless(defaultTab: DoeTab | "all"): HeadlessPaneDefinition<
|
|
|
95
106
|
metadata: { ...data, series: [row], complete: data.status === "available" },
|
|
96
107
|
};
|
|
97
108
|
}
|
|
98
|
-
const tabs =
|
|
109
|
+
const tabs = gasWord ? ["gas" as const]
|
|
110
|
+
: args.options.tab === "all" || args.options.tab == null ? DOE_TABS.map(({ value }) => value) : [doeTab(args.options.tab)];
|
|
99
111
|
return {
|
|
112
|
+
...(gasWord && !ngs ? { warnings: [`Gas storage is its own function: gloom fn NGS (one region: gloom fn NGS east)`] } : {}),
|
|
100
113
|
sections: tabs.map((tab) => ({
|
|
101
114
|
title: `${DOE_TABS.find((entry) => entry.value === tab)!.label}${doeHeaderLine(doeReport(data, tab)) ? `: ${doeHeaderLine(doeReport(data, tab))}` : ""}`,
|
|
102
115
|
columns: SUMMARY_COLUMNS,
|
|
@@ -4,15 +4,16 @@ import type { PluginModule } from "../plugin-module";
|
|
|
4
4
|
import { DOE_CATALOG_SERIES } from "./series";
|
|
5
5
|
import { doeBoardCache } from "./client";
|
|
6
6
|
import { doePriceCache } from "./prices";
|
|
7
|
-
import { doeHeadless } from "./headless";
|
|
7
|
+
import { doeGasWord, doeHeadless } from "./headless";
|
|
8
8
|
import { DOE_SERIES_OPTIONS, doeSeriesOption } from "./model";
|
|
9
|
-
import { DoePane } from "./pane";
|
|
10
9
|
|
|
11
10
|
/** Opens on a tab, or on the series typed after the mnemonic and its tab. */
|
|
12
11
|
function doeInstance(tab: DoeTab, options?: PaneTemplateCreateOptions): PaneTemplateInstanceConfig {
|
|
13
12
|
const input = options?.arg?.trim();
|
|
13
|
+
// `DOE NGS` or `DOE natural gas` opens on gas storage, as `NGS` does.
|
|
14
|
+
if (input && doeGasWord(input)) return { placement: "floating", settings: { tab: "gas" } };
|
|
14
15
|
const series = doeSeriesOption(input);
|
|
15
|
-
if (input && !series) throw new Error(
|
|
16
|
+
if (input && !series) throw new Error(`Use a series such as cushing, gasoline, distillate or east${tab === "gas" ? "" : "; for gas storage, gloom fn NGS"}`);
|
|
16
17
|
return { placement: "floating", settings: { tab: series?.tab ?? tab }, ...(series ? { params: { series: series.value } } : {}) };
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -23,7 +24,7 @@ const shortcut = (prefix: string, tab: DoeTab | null) => ({
|
|
|
23
24
|
|
|
24
25
|
export const doeModule: PluginModule = {
|
|
25
26
|
series: DOE_CATALOG_SERIES,
|
|
26
|
-
panes: [{ id: "doe", name: "Oil and Gas Inventories", icon: "D", component: DoePane,
|
|
27
|
+
panes: [{ id: "doe", name: "Oil and Gas Inventories", icon: "D", get component(): typeof import("./pane").DoePane { const { DoePane } = require("./pane"); return DoePane; },
|
|
27
28
|
defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 96, height: 34 },
|
|
28
29
|
tableExport: true, headless: doeHeadless("all") }],
|
|
29
30
|
paneTemplates: [{
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type QueryBarFilter,
|
|
11
11
|
type QueryBarView,
|
|
12
12
|
} from "../../../components";
|
|
13
|
+
import { ageFooterSegment } from "../../../components/layout/pane/footer";
|
|
13
14
|
import { handleRefreshKey, usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
14
15
|
import { openFormModal } from "../../../components/form-modal";
|
|
15
16
|
import { FILL_REST } from "../../../components/ui/fill";
|
|
@@ -305,7 +306,7 @@ export function EarningsBoard({ focused, width, height, scopedSymbols, highlight
|
|
|
305
306
|
loading: resource.loading,
|
|
306
307
|
error: resource.error ?? data?.refreshError ?? null,
|
|
307
308
|
stale: !!data?.stale,
|
|
308
|
-
info: data && updatedAgo ? [
|
|
309
|
+
info: data && updatedAgo ? [ageFooterSegment(updatedAgo)] : [],
|
|
309
310
|
});
|
|
310
311
|
const symbol = open?.symbol ?? selectedReport?.symbol ?? null;
|
|
311
312
|
// The desktop and the web only: the terminal's footer stays as it was.
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
usePaneStatusFooter,
|
|
9
9
|
type ChartTableChart,
|
|
10
10
|
} from "../../../components";
|
|
11
|
+
import { ageFooterSegment } from "../../../components/layout/pane/footer";
|
|
11
12
|
import { staticSeries } from "../../../components/chart/static/series";
|
|
12
13
|
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
13
14
|
import { useAsyncResource, useAutoRefresh, usePluginPaneState, useUpdatedAgo } from "../../../public/react";
|
|
@@ -107,7 +108,7 @@ export function EarningsHistoryView({ symbol, width, height, focused, registrati
|
|
|
107
108
|
error: historyError,
|
|
108
109
|
stale: !!resource.data?.stale,
|
|
109
110
|
info: abroad ? [{ id: "us-listings", parts: [{ text: "past reports: US listings only", tone: "muted" as const }] }]
|
|
110
|
-
: payload && updatedAgo ? [
|
|
111
|
+
: payload && updatedAgo ? [ageFooterSegment(updatedAgo)] : [],
|
|
111
112
|
});
|
|
112
113
|
|
|
113
114
|
return (
|
|
@@ -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
|
/**
|
|
@@ -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) {
|