@gloom-sh/terminal 0.18.5 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +140 -7
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +17 -6
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/index.ts +11 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +1 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/research-activity.ts +3 -2
- package/src/api-client/types.ts +70 -2
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +0 -2
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/pane-tabs-allowlist.json +3 -0
- package/src/architecture/ttl-caches-allowlist.json +19 -0
- package/src/brokers/sync-broker-instance.ts +2 -8
- package/src/capabilities/chart-series.ts +1 -1
- package/src/cli/commands/crypto-hints.ts +40 -24
- package/src/cli/commands/market.ts +72 -26
- package/src/cli/commands/options-chain.ts +48 -7
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +61 -12
- package/src/cli/commands/search.ts +21 -1
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +55 -34
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +8 -0
- package/src/cli/glossary.ts +25 -2
- package/src/cli/history-rows.ts +57 -9
- package/src/cli/index.ts +2 -0
- package/src/cli/listing-arg.ts +180 -52
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +3 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +86 -3
- package/src/cli/pane-functions/function-help.ts +13 -10
- package/src/cli/pane-functions/headless.ts +17 -7
- package/src/cli/pane-functions/index.ts +49 -14
- package/src/cli/pane-functions/market-session.ts +66 -4
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/resolver.ts +19 -8
- package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
- package/src/cli/pane-functions/screenshot-types.ts +5 -0
- package/src/cli/quote-answer.ts +22 -11
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +4 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-surface.tsx +52 -3
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +39 -4
- package/src/components/chart/composite/series-cues.ts +14 -0
- package/src/components/chart/composite/text-renderer.ts +9 -2
- package/src/components/chart/composite/tool-catalog.ts +36 -0
- package/src/components/chart/composite/tools.ts +203 -44
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
- package/src/components/command-bar/assist/inventory.ts +2 -1
- package/src/components/command-bar/commands/direct/index.ts +28 -13
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
- package/src/components/onboarding/wizard-broker-sync.ts +0 -1
- package/src/components/onboarding/wizard-positions.ts +3 -3
- package/src/components/ticker/list-table-view.tsx +4 -1
- package/src/components/ui/choice-dialog.tsx +23 -18
- package/src/components/ui/choice-groups.ts +18 -0
- package/src/components/ui/data-table/opentui/index.tsx +9 -7
- package/src/components/ui/data-table/opentui/model.ts +26 -1
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/list-view.tsx +8 -4
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +1 -0
- package/src/components/ui/select-button.tsx +5 -1
- package/src/components/ui/select-field.tsx +1 -0
- package/src/components/ui/table-layout.ts +8 -5
- package/src/core/app-runtime.ts +2 -0
- package/src/core/app-services.ts +2 -2
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/ticker-repository.ts +16 -9
- package/src/data/ticker-writes.ts +81 -0
- package/src/data/ttl-cache.ts +165 -0
- package/src/docs/snapshot.json +71 -137
- 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/market-data/depositary-pair.ts +2 -2
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +16 -3
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/options-alternatives.ts +81 -4
- package/src/news/aggregator.ts +113 -9
- package/src/news/cloud-session.ts +21 -0
- package/src/news/hooks.ts +1 -0
- package/src/news/news-model.ts +1 -0
- package/src/news/types.ts +11 -0
- package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +336 -77
- package/src/plugins/builtin/alerts/command.ts +25 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- package/src/plugins/builtin/alerts/fired-log.ts +73 -9
- package/src/plugins/builtin/alerts/format.ts +75 -5
- package/src/plugins/builtin/alerts/headless.ts +24 -6
- package/src/plugins/builtin/alerts/history-rows.ts +3 -2
- package/src/plugins/builtin/alerts/history.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +79 -16
- package/src/plugins/builtin/alerts/metric-command.ts +77 -4
- package/src/plugins/builtin/alerts/metric-data.ts +54 -13
- package/src/plugins/builtin/alerts/metric-model.ts +163 -14
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
- package/src/plugins/builtin/alerts/pane.tsx +61 -23
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +12 -8
- package/src/plugins/builtin/alerts/types.ts +4 -0
- package/src/plugins/builtin/analytics/index.tsx +2 -1
- package/src/plugins/builtin/analytics/risk-client.ts +16 -5
- package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
- package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
- package/src/plugins/builtin/analytics/risk-model.ts +55 -9
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/attention/index.tsx +2 -1
- package/src/plugins/builtin/attention/listing.ts +14 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/buildout/cells.tsx +12 -4
- package/src/plugins/builtin/buildout/format.ts +38 -0
- package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
- package/src/plugins/builtin/buildout/table-model.ts +23 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
- package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
- package/src/plugins/builtin/chart-composer/headless.ts +6 -1
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
- package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
- package/src/plugins/builtin/clinical-trials/model.ts +0 -9
- package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/company-attention/building.tsx +5 -1
- package/src/plugins/builtin/company-attention/client.ts +8 -9
- package/src/plugins/builtin/company-attention/evidence.ts +4 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +9 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/correlation/headless.ts +13 -5
- package/src/plugins/builtin/correlation/history.ts +22 -0
- package/src/plugins/builtin/cot/client.ts +22 -18
- package/src/plugins/builtin/cot/headless.ts +15 -10
- package/src/plugins/builtin/cot/model.ts +14 -1
- package/src/plugins/builtin/cot/pane.tsx +22 -14
- package/src/plugins/builtin/credit-boards/client.ts +6 -10
- package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
- package/src/plugins/builtin/credit-conditions/model.ts +4 -0
- package/src/plugins/builtin/credit-conditions/series.ts +3 -1
- package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
- package/src/plugins/builtin/credit-documents/export.ts +173 -0
- package/src/plugins/builtin/credit-documents/headless.ts +7 -7
- package/src/plugins/builtin/credit-documents/model.ts +104 -14
- package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
- package/src/plugins/builtin/custom-view/sources.ts +2 -0
- package/src/plugins/builtin/dividend-yield/client.ts +131 -23
- package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
- package/src/plugins/builtin/dividend-yield/model.ts +8 -5
- package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
- package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
- package/src/plugins/builtin/dividend-yield/types.ts +20 -2
- package/src/plugins/builtin/dividend-yield/view.ts +30 -2
- package/src/plugins/builtin/docs/targets.ts +5 -1
- package/src/plugins/builtin/earnings/board-model.ts +16 -15
- package/src/plugins/builtin/earnings/board.tsx +62 -71
- package/src/plugins/builtin/earnings/headless.ts +40 -4
- package/src/plugins/builtin/earnings/scope.ts +31 -43
- package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
- package/src/plugins/builtin/econ/types.ts +2 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +6 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +36 -9
- package/src/plugins/builtin/equity-screener/categories.ts +179 -0
- package/src/plugins/builtin/equity-screener/client.ts +4 -2
- package/src/plugins/builtin/equity-screener/headless.ts +144 -13
- package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
- package/src/plugins/builtin/equity-screener/model.ts +99 -10
- package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
- package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
- package/src/plugins/builtin/fx-matrix/client.ts +6 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
- package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
- package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
- package/src/plugins/builtin/gpu/headless.ts +8 -2
- package/src/plugins/builtin/help/function-index.ts +4 -1
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +18 -1
- package/src/plugins/builtin/iv-history/client.ts +52 -11
- package/src/plugins/builtin/iv-history/format.ts +36 -23
- package/src/plugins/builtin/iv-history/headless.ts +33 -18
- package/src/plugins/builtin/iv-history/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/model.ts +163 -27
- package/src/plugins/builtin/iv-history/pane.tsx +10 -6
- package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
- package/src/plugins/builtin/iv-history/universe.ts +5 -1
- package/src/plugins/builtin/jobs/client.ts +25 -29
- package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
- package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
- package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
- package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
- package/src/plugins/builtin/macro-day/release-table.ts +2 -2
- package/src/plugins/builtin/market-heatmap/data.ts +8 -33
- package/src/plugins/builtin/market-movers/client.ts +25 -7
- package/src/plugins/builtin/market-movers/headless.ts +32 -12
- package/src/plugins/builtin/market-movers/index.tsx +26 -14
- package/src/plugins/builtin/market-movers/min-float.ts +74 -0
- package/src/plugins/builtin/market-movers/model.ts +4 -1
- package/src/plugins/builtin/market-movers/screener.ts +14 -0
- package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
- package/src/plugins/builtin/market-movers/session-table.ts +4 -3
- package/src/plugins/builtin/market-movers/table.ts +17 -5
- package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
- package/src/plugins/builtin/news/index.tsx +1 -0
- package/src/plugins/builtin/news/wire/categories.ts +19 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
- package/src/plugins/builtin/openfda/client.ts +14 -2
- package/src/plugins/builtin/openfda/index.tsx +3 -2
- package/src/plugins/builtin/openfda/model.ts +9 -0
- package/src/plugins/builtin/openfda/pane.tsx +42 -6
- package/src/plugins/builtin/options/analytics.ts +29 -5
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/table.ts +5 -2
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +17 -4
- package/src/plugins/builtin/options-positioning/client.ts +11 -0
- package/src/plugins/builtin/options-positioning/headless.ts +21 -8
- package/src/plugins/builtin/options-positioning/index.tsx +1 -1
- package/src/plugins/builtin/options-positioning/model.ts +78 -9
- package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
- package/src/plugins/builtin/options-scenario/client.ts +116 -47
- package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
- package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
- package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
- package/src/plugins/builtin/options-scenario/csp.ts +8 -2
- package/src/plugins/builtin/options-scenario/index.tsx +9 -7
- package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
- package/src/plugins/builtin/pe-band/model.ts +12 -49
- package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
- package/src/plugins/builtin/perps/cascade.ts +236 -0
- package/src/plugins/builtin/perps/client.ts +56 -2
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +19 -22
- package/src/plugins/builtin/perps/status-line.ts +25 -0
- package/src/plugins/builtin/plugin-module.ts +5 -1
- package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/client.ts +18 -4
- package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
- package/src/plugins/builtin/relative-rotation/model.ts +16 -40
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
- package/src/plugins/builtin/research/event-model.ts +10 -8
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +7 -5
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
- package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
- package/src/plugins/builtin/scanner/flow-model.ts +50 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
- package/src/plugins/builtin/scanner/flow-session.ts +146 -0
- package/src/plugins/builtin/scanner/headless.ts +204 -35
- package/src/plugins/builtin/scanner/index.ts +16 -5
- package/src/plugins/builtin/scanner/live-headless.ts +27 -3
- package/src/plugins/builtin/scanner/live-model.ts +11 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/headless.ts +10 -1
- package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
- package/src/plugins/builtin/sec/fund-facts.ts +116 -34
- package/src/plugins/builtin/sec/headless.ts +2 -1
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
- package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
- package/src/plugins/builtin/shared/earnings-report.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/members-menu.tsx +3 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
- package/src/plugins/builtin/shared/report-freshness.ts +3 -2
- package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
- package/src/plugins/builtin/shared/ticker-company.ts +77 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/model.ts +16 -7
- package/src/plugins/builtin/themes/pane.tsx +5 -5
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
- package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
- package/src/plugins/builtin/time-sales/pane.tsx +31 -19
- package/src/plugins/builtin/treasury-daily/client.ts +41 -5
- package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
- package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
- package/src/plugins/builtin/treasury-daily/model.ts +50 -3
- package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
- package/src/plugins/builtin/vol-surface/client.ts +3 -13
- package/src/plugins/builtin/vol-surface/headless.ts +3 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +2 -2
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/remote/resources.ts +2 -1
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/list-view.tsx +13 -10
- package/src/renderers/dom/desktop/query-bar.tsx +4 -2
- package/src/renderers/dom/desktop/select-field.tsx +4 -2
- package/src/renderers/dom/host/chart-surface.tsx +21 -0
- package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
- package/src/renderers/electrobun/bun/desktop/backend.ts +12 -0
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/shared/app-name.ts +13 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +3 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/series/registry.ts +1 -0
- package/src/series/types.ts +6 -0
- package/src/series/validate.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +1 -0
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/index.ts +8 -5
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/controller.ts +4 -3
- package/src/sync/core-contributors.ts +90 -31
- package/src/telemetry/stall-reports-dom.ts +38 -0
- package/src/telemetry/stall-reports-node.ts +75 -0
- package/src/telemetry/stall-reports.ts +214 -0
- package/src/test-support/preload.ts +14 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name.ts +44 -3
- package/src/tickers/listing-venues.ts +15 -3
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +1 -1
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/fundamentals.ts +44 -1
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/resolve-context.ts +3 -1
- package/src/time-series/resolve.ts +56 -4
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +3 -2
- package/src/time-series/studies.ts +8 -2
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +2 -3
- package/src/time-series/types.ts +22 -1
- package/src/types/financials.ts +32 -1
- package/src/types/headless.ts +21 -3
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +9 -0
- package/src/ui/host.tsx +3 -0
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +44 -0
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +24 -3
- package/src/utils/fx-market-hours.ts +26 -3
- package/src/utils/main-thread-stall.ts +35 -2
- package/src/utils/market-capitalization.ts +24 -1
- package/src/utils/price-history.ts +7 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
KPI_FIELDS,
|
|
5
5
|
NUMERIC_FIELDS,
|
|
6
6
|
type NumericField,
|
|
7
|
+
type ScreenMetric,
|
|
8
|
+
type ScreenPayload,
|
|
7
9
|
type ScreenRow,
|
|
8
10
|
} from "../../../api-client/equity-screener";
|
|
9
11
|
import { KPI_COVERAGE_NOTE } from "../company-kpis/model";
|
|
@@ -12,9 +14,19 @@ import { buildQuoteKey } from "../../../market-data/selectors";
|
|
|
12
14
|
import type { Quote } from "../../../types/financials";
|
|
13
15
|
import { formatCurrency, formatPercentRaw } from "../../../utils/format";
|
|
14
16
|
import { fetchSavedScreens, fetchScreen, fetchScreenCheck } from "./client";
|
|
17
|
+
import { SCREEN_IDEAS, screenIdea } from "./ideas";
|
|
15
18
|
import { checkRows, screenCheckSummary, screenCheckTicker } from "./screen-check";
|
|
16
19
|
import { overlayScreenQuotes, screenExtendedSessions, sortLiveScreenRows } from "./live";
|
|
17
20
|
import {
|
|
21
|
+
CATEGORY_NAMES_POINTER,
|
|
22
|
+
SCREEN_INDUSTRIES,
|
|
23
|
+
SCREEN_SECTORS,
|
|
24
|
+
unmatchedCategoryText,
|
|
25
|
+
unmatchedCategoryValues,
|
|
26
|
+
} from "./categories";
|
|
27
|
+
import { REPORT_ROWS } from "../shared/headless-options";
|
|
28
|
+
import {
|
|
29
|
+
appendScreenPage,
|
|
18
30
|
DEFAULT_SCREEN,
|
|
19
31
|
EXAMPLE_SCREEN_DEFINITION,
|
|
20
32
|
formatScreenMetric,
|
|
@@ -71,6 +83,75 @@ function extendedColumn(session: ExtendedSession): HeadlessPaneColumn {
|
|
|
71
83
|
};
|
|
72
84
|
}
|
|
73
85
|
|
|
86
|
+
/** Rows a report lists without `--limit`: one page, as the pane loads before it pages. */
|
|
87
|
+
const SCREEN_PAGE_ROWS = 100;
|
|
88
|
+
/** The most rows `--limit` lists, read a page at a time. */
|
|
89
|
+
const SCREEN_ROW_LIMIT = 500;
|
|
90
|
+
/** The most rows the server returns in one page. */
|
|
91
|
+
const SCREEN_MAX_PAGE = 200;
|
|
92
|
+
|
|
93
|
+
/** The rows a report lists: pages of the screen, in its order, until `limit` rows or the last match. */
|
|
94
|
+
async function fetchScreenRows(definition: ScreenDefinition, limit: number, ctx: HeadlessPaneContext): Promise<ScreenPayload> {
|
|
95
|
+
let data = await fetchScreen(definition, null, ctx.signal, ctx.apiClient, Math.min(limit, SCREEN_MAX_PAGE));
|
|
96
|
+
while (data.rows.length < limit && data.nextCursor) {
|
|
97
|
+
const page = await fetchScreen(definition, data.nextCursor, ctx.signal, ctx.apiClient, Math.min(limit - data.rows.length, SCREEN_MAX_PAGE));
|
|
98
|
+
data = { ...appendScreenPage(data, page), warnings: [...new Set([...data.warnings, ...page.warnings])] };
|
|
99
|
+
}
|
|
100
|
+
return data;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** What a report of the first rows says of the rest. */
|
|
104
|
+
function moreMatchesNotice(shown: number, matched: number): string {
|
|
105
|
+
return matched <= SCREEN_ROW_LIMIT
|
|
106
|
+
? `Top ${shown} of ${matched} matches; --limit ${matched} lists them all.`
|
|
107
|
+
: `Top ${shown} of ${matched} matches; --limit lists up to ${SCREEN_ROW_LIMIT}, or narrow the criteria to see the rest.`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const isNumericField = (field: string): field is NumericField => (NUMERIC_FIELDS as readonly string[]).includes(field);
|
|
111
|
+
const metricToken = (value: string) => value.trim().toLowerCase().replace(/[\s_-]+/g, "");
|
|
112
|
+
|
|
113
|
+
/** `--metric trailingPE,dividendYieldPercent` as the fields it names, each once, in order; an unknown one fails. */
|
|
114
|
+
function readMetrics(value: string): NumericField[] {
|
|
115
|
+
const fields = value.split(",").map((part) => part.trim()).filter(Boolean).map((part) => {
|
|
116
|
+
const field = NUMERIC_FIELDS.find((candidate) => metricToken(candidate) === metricToken(part));
|
|
117
|
+
if (!field) throw new Error(`Invalid --metric value "${part}". Use one of: ${NUMERIC_FIELDS.join(", ")}.`);
|
|
118
|
+
return field;
|
|
119
|
+
});
|
|
120
|
+
if (fields.length === 0) throw new Error("--metric requires a value, such as --metric trailingPE.");
|
|
121
|
+
return [...new Set(fields)];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
type ShownCategory = "sector" | "industry" | "currency";
|
|
125
|
+
const CATEGORY_HEADERS: Record<ShownCategory, string> = { sector: "Sector", industry: "Industry", currency: "Currency" };
|
|
126
|
+
/** Columns every report prints, so neither another metric nor the sort field adds them again. */
|
|
127
|
+
const ALWAYS_SHOWN = new Set(["symbol", "exchange", "name", "price", "changePercent"]);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The columns a report adds after the first metric's: the other metrics
|
|
131
|
+
* asked for, then the field the screen is sorted by, so the order of the
|
|
132
|
+
* rows is explained by a column (P/E focus, sorted by dividend yield). A
|
|
133
|
+
* column every report prints (Last, Chg%) is not added twice, and its raw
|
|
134
|
+
* number stays in the row.
|
|
135
|
+
*/
|
|
136
|
+
function extraScreenColumns(metrics: readonly NumericField[], sortField: string): Array<NumericField | ShownCategory> {
|
|
137
|
+
return [...new Set([...metrics.slice(1), sortField as NumericField | ShownCategory])]
|
|
138
|
+
.filter((field) => field !== metrics[0] && !ALWAYS_SHOWN.has(field));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Another metric's cell: its value as the focus column prints it, with the currency of a money value. */
|
|
142
|
+
function extraMetricCell(field: NumericField, metric: ScreenMetric | undefined): string {
|
|
143
|
+
if (!metric) return "-";
|
|
144
|
+
if (metric.access === "pro") return "locked";
|
|
145
|
+
const value = formatScreenMetric(field, metric);
|
|
146
|
+
return metric.value != null && /^[A-Z]{3}$/.test(metric.unit) ? `${value} ${metric.unit}` : value;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** What the State column's words mean, said once under a report whose first metric has them. */
|
|
150
|
+
const STATE_LEGEND: ReadonlyArray<{ state: ScreenMetric["state"]; text: string }> = [
|
|
151
|
+
{ state: "partial", text: "A partial State means the source gives no date for the figure, so As of is when it was collected, or that it covers part of what it measures." },
|
|
152
|
+
{ state: "stale", text: "A stale State means the figure is older than its field allows, such as a quote over 4 days old or fundamentals collected over 7 days ago." },
|
|
153
|
+
];
|
|
154
|
+
|
|
74
155
|
/** A typed `--definition`, checked; every error points at the worked example. */
|
|
75
156
|
function readDefinition(text: string): ScreenDefinition {
|
|
76
157
|
const pointer = "The example in gloom catalog EQS is a valid definition to start from.";
|
|
@@ -112,6 +193,14 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
112
193
|
shot: { reportOnly: "opens a saved screen for the report; open it from Saved in the EQS pane" },
|
|
113
194
|
replaces: ["definition"],
|
|
114
195
|
},
|
|
196
|
+
{
|
|
197
|
+
key: "preset",
|
|
198
|
+
type: "enum",
|
|
199
|
+
description: `A ready screen from Ideas, ranked by its own metric unless --metric says otherwise: ${SCREEN_IDEAS.map((entry) => `${entry.id} (${entry.name})`).join(", ")}. Cannot be combined with --saved or --definition.`,
|
|
200
|
+
values: SCREEN_IDEAS.map((entry) => ({ value: entry.id })),
|
|
201
|
+
example: "--preset golden-cross",
|
|
202
|
+
replaces: ["definition", "saved"],
|
|
203
|
+
},
|
|
115
204
|
{
|
|
116
205
|
key: "definition",
|
|
117
206
|
type: "string",
|
|
@@ -126,15 +215,32 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
126
215
|
{
|
|
127
216
|
key: "metric",
|
|
128
217
|
type: "enum",
|
|
218
|
+
list: true,
|
|
219
|
+
// No default: a preset leads with its own metric, anything else with marketCap.
|
|
129
220
|
description:
|
|
130
|
-
"Metric to display with its source date and covered-universe rank.",
|
|
221
|
+
"Metric to display with its source date and covered-universe rank: marketCap, or the preset's own metric. Several, as a comma list or the flag again, add a column each after the first.",
|
|
222
|
+
example: "--metric trailingPE --metric dividendYieldPercent",
|
|
131
223
|
values: NUMERIC_FIELDS.map((value) => ({ value })),
|
|
132
|
-
|
|
224
|
+
normalize: (value) => readMetrics(value).join(","),
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
key: "limit",
|
|
228
|
+
type: "integer",
|
|
229
|
+
shot: REPORT_ROWS,
|
|
230
|
+
minimum: 1,
|
|
231
|
+
maximum: SCREEN_ROW_LIMIT,
|
|
232
|
+
defaultValue: SCREEN_PAGE_ROWS,
|
|
233
|
+
description: `Matching rows to list, in the screen's order, up to ${SCREEN_ROW_LIMIT}.`,
|
|
234
|
+
example: "--limit 40",
|
|
133
235
|
},
|
|
134
236
|
],
|
|
135
237
|
describe: "Equity screener",
|
|
136
238
|
discovery: {
|
|
137
239
|
aliases: ["EQS"],
|
|
240
|
+
reference: [
|
|
241
|
+
{ title: "Sectors", items: [...SCREEN_SECTORS] },
|
|
242
|
+
{ title: "Industries", items: [...SCREEN_INDUSTRIES] },
|
|
243
|
+
],
|
|
138
244
|
dataRequirements: ["Gloom Cloud stored screener snapshot"],
|
|
139
245
|
limitations: [
|
|
140
246
|
"Covered stored universe, not every listed equity.",
|
|
@@ -142,13 +248,18 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
142
248
|
"Observed-subset insider activity cannot establish an absence.",
|
|
143
249
|
"ATR% is the 14-session average true range as a percent of the last close, for US listings with at least 15 completed daily sessions.",
|
|
144
250
|
"IV rank is a rank, not an IV percentile: 0 is the lowest 30-day implied volatility of the past 52 weeks and 100 the highest.",
|
|
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
|
+
"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
|
+
"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.",
|
|
145
254
|
],
|
|
146
255
|
},
|
|
147
256
|
async load(args, ctx) {
|
|
148
|
-
if (args.options.saved
|
|
149
|
-
|
|
257
|
+
if ([args.options.preset, args.options.saved, args.options.definition].filter(Boolean).length > 1)
|
|
258
|
+
throw new Error(args.options.preset ? "Choose one of --preset, --saved and --definition." : "Choose --saved or --definition, not both.");
|
|
259
|
+
const preset = screenIdea(args.options.preset == null ? undefined : String(args.options.preset));
|
|
260
|
+
let definition = preset?.definition ?? (args.options.definition
|
|
150
261
|
? readDefinition(String(args.options.definition))
|
|
151
|
-
: DEFAULT_SCREEN;
|
|
262
|
+
: DEFAULT_SCREEN);
|
|
152
263
|
let savedName: string | undefined;
|
|
153
264
|
if (args.options.saved) {
|
|
154
265
|
const screens = await fetchSavedScreens(ctx.signal, ctx.apiClient);
|
|
@@ -189,8 +300,10 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
189
300
|
},
|
|
190
301
|
};
|
|
191
302
|
}
|
|
192
|
-
const
|
|
193
|
-
const
|
|
303
|
+
const limit = typeof args.options.limit === "number" ? args.options.limit : SCREEN_PAGE_ROWS;
|
|
304
|
+
const data = await fetchScreenRows(definition, limit, ctx);
|
|
305
|
+
const metrics = readMetrics(args.options.metric == null ? preset?.metric ?? "marketCap" : String(args.options.metric));
|
|
306
|
+
const metric = metrics[0]!;
|
|
194
307
|
// Last, Chg% and market cap are the regular session, as `quote` and the
|
|
195
308
|
// pane read each row's quote; an extended print is a column of its own.
|
|
196
309
|
// A sort on one of them is redone on those figures.
|
|
@@ -204,21 +317,33 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
204
317
|
const kpiGaps = !withheld && missing > 0 && (KPI_FIELDS as readonly string[]).includes(metric)
|
|
205
318
|
? [`${missing} of ${rows.length} ${rows.length === 1 ? "row has" : "rows have"} no ${screenLabel(metric)}. ${KPI_COVERAGE_NOTE}`]
|
|
206
319
|
: [];
|
|
320
|
+
// A category value that names nothing (`Utility` for Utilities) says so with the names it was likely meant as.
|
|
321
|
+
const unmatched = unmatchedCategoryValues(data.definition, data, rows).map(unmatchedCategoryText);
|
|
322
|
+
const unmatchedText = unmatched.length > 0 ? [...unmatched, CATEGORY_NAMES_POINTER].join(" ") : null;
|
|
207
323
|
// A CAD screen of listings quoted in USD is empty for its currency, not for want of companies: say so, and give the screen in USD.
|
|
208
324
|
const currencyEmpty = rows.length === 0 ? screenCurrencyEmpty(data.definition, data.universe) : null;
|
|
209
|
-
const empty =
|
|
325
|
+
const empty = rows.length === 0 && unmatchedText
|
|
326
|
+
? unmatchedText
|
|
327
|
+
: currencyEmpty
|
|
210
328
|
? [
|
|
211
329
|
`${currencyEmpty.title} ${currencyEmpty.detail}`,
|
|
212
330
|
`In the base currency, ${currencyEmpty.base.currency}: gloom fn EQS --definition '${spacedJson(currencyEmpty.base)}'${metric === "marketCap" ? "" : ` --metric ${metric}`}`,
|
|
213
331
|
].join("\n")
|
|
214
332
|
: null;
|
|
333
|
+
const ytdBase = screenYtdBase(data.rows, data.snapshot?.assembledAt);
|
|
334
|
+
// The metrics after the first, then the field the rows are sorted by, so the order shown is explained by a column.
|
|
335
|
+
const extra = extraScreenColumns(metrics, data.definition.sort.field);
|
|
336
|
+
const states = new Set(rows.map((row) => row.metrics[metric].state));
|
|
337
|
+
const legend = STATE_LEGEND.filter(({ state }) => states.has(state)).map(({ text }) => text);
|
|
338
|
+
const notes = [...kpiGaps, ...legend];
|
|
215
339
|
// Payload status also counts ordinary per-field sparsity; only generation
|
|
216
340
|
// warnings are failures, and more pages are not an error.
|
|
217
341
|
return {
|
|
218
342
|
freshness: { asOf: data.snapshot?.sourceNewestAt ?? null },
|
|
219
343
|
complete: !data.nextCursor && errors.length === 0,
|
|
220
344
|
errors,
|
|
221
|
-
...(
|
|
345
|
+
...(notes.length ? { notes } : {}),
|
|
346
|
+
...(rows.length > 0 && unmatchedText ? { warnings: [unmatchedText] } : {}),
|
|
222
347
|
sections: [
|
|
223
348
|
{
|
|
224
349
|
title: "Matching equities",
|
|
@@ -232,11 +357,14 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
232
357
|
...(metric === "changePercent" ? [] : [{ key: "changePercent", header: "Chg%", align: "right" as const, format: percent }]),
|
|
233
358
|
...extendedSessions.map(extendedColumn),
|
|
234
359
|
// YTD names the close it is measured from: `YTD% vs 31 Dec close`.
|
|
235
|
-
{ key: "value", header: screenColumnLabel(metric,
|
|
360
|
+
{ key: "value", header: screenColumnLabel(metric, ytdBase, "report"), align: "right" },
|
|
236
361
|
{ key: "unit", header: "Unit" },
|
|
237
362
|
{ key: "percentile", header: "Covered pctl", align: "right" },
|
|
238
363
|
{ key: "asOf", header: "As of" },
|
|
239
364
|
{ key: "state", header: "State" },
|
|
365
|
+
...extra.map((field): HeadlessPaneColumn => isNumericField(field)
|
|
366
|
+
? { key: field, header: screenColumnLabel(field, ytdBase, "report"), align: "right" }
|
|
367
|
+
: { key: field, header: CATEGORY_HEADERS[field] }),
|
|
240
368
|
],
|
|
241
369
|
rows: rows.map((row) => {
|
|
242
370
|
const observation = row.metrics[metric];
|
|
@@ -260,6 +388,9 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
260
388
|
: Math.round(observation.percentile.value),
|
|
261
389
|
asOf: stamp.collected ? `${stamp.text} (collected)` : stamp.text,
|
|
262
390
|
state: observation.access === "pro" ? "locked" : observation.state,
|
|
391
|
+
...Object.fromEntries(extra.map((field) => [field, isNumericField(field)
|
|
392
|
+
? extraMetricCell(field, row.metrics[field])
|
|
393
|
+
: row[field]])),
|
|
263
394
|
};
|
|
264
395
|
}),
|
|
265
396
|
},
|
|
@@ -272,11 +403,11 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
272
403
|
covered: data.universe.covered,
|
|
273
404
|
returned: data.rows.length,
|
|
274
405
|
hasMore: !!data.nextCursor,
|
|
406
|
+
metrics,
|
|
407
|
+
...(unmatched.length > 0 ? { unmatchedValues: unmatchedCategoryValues(data.definition, data, rows) } : {}),
|
|
275
408
|
priceBasis: "price, changePercent and the market cap are the regular session, read from each row's quote as `quote` reads it: after the close, its close and that close's move. A pre-market, after-hours or overnight print is in extendedPrice and extendedChangePercent, measured from that close. Membership and percentiles are the snapshot's, which ranks by the close; a sort on price, changePercent, volume or marketCap orders these rows by the figures shown.",
|
|
276
409
|
notices: data.nextCursor
|
|
277
|
-
? [
|
|
278
|
-
`Top ${data.rows.length} of ${data.universe.matched} matches; narrow the criteria to see the rest.`,
|
|
279
|
-
]
|
|
410
|
+
? [moreMatchesNotice(data.rows.length, data.universe.matched)]
|
|
280
411
|
: [],
|
|
281
412
|
},
|
|
282
413
|
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { NumericField, ScreenCriterion, ScreenDefinition } from "../../../api-client/equity-screener";
|
|
2
|
+
|
|
3
|
+
/** A ready screen in the Ideas tab and `--preset`: its definition and the metric it leads with. */
|
|
4
|
+
export interface ScreenIdea {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
metric: NumericField;
|
|
8
|
+
definition: ScreenDefinition;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Companies above 2B, so an idea lists names that trade, not every small listing that moved. */
|
|
12
|
+
const TRADED: ScreenCriterion = { field: "marketCap", op: "gte", value: 2_000_000_000 };
|
|
13
|
+
|
|
14
|
+
const idea = (
|
|
15
|
+
id: string,
|
|
16
|
+
name: string,
|
|
17
|
+
criteria: ScreenCriterion[],
|
|
18
|
+
sort: ScreenDefinition["sort"] & { field: NumericField },
|
|
19
|
+
): ScreenIdea => ({
|
|
20
|
+
id,
|
|
21
|
+
name,
|
|
22
|
+
metric: sort.field,
|
|
23
|
+
definition: { version: 1, currency: "USD", criteria: [...criteria, TRADED], sort },
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/** Technical ideas, read at the last completed session. */
|
|
27
|
+
export const SCREEN_IDEAS: ScreenIdea[] = [
|
|
28
|
+
idea("oversold-uptrend", "Oversold in an uptrend", [
|
|
29
|
+
{ field: "rsi14", op: "lte", value: 30 },
|
|
30
|
+
{ field: "priceVsSma200Percent", op: "gt", value: 0 },
|
|
31
|
+
], { field: "rsi14", direction: "asc" }),
|
|
32
|
+
idea("golden-cross", "Fresh golden cross", [
|
|
33
|
+
{ field: "goldenCrossDays", op: "lte", value: 10 },
|
|
34
|
+
], { field: "goldenCrossDays", direction: "asc" }),
|
|
35
|
+
// Every match is at its high, so the largest companies lead.
|
|
36
|
+
idea("52-week-high", "52-week high breakout", [
|
|
37
|
+
{ field: "newHigh52wDays", op: "eq", value: 0 },
|
|
38
|
+
], { field: "marketCap", direction: "desc" }),
|
|
39
|
+
idea("bollinger-squeeze", "Bollinger squeeze", [
|
|
40
|
+
{ field: "bollingerWidthPercentile", op: "lte", value: 5 },
|
|
41
|
+
], { field: "bollingerWidthPercentile", direction: "asc" }),
|
|
42
|
+
idea("macd-turn", "MACD turns up in an uptrend", [
|
|
43
|
+
{ field: "macdBullishCrossDays", op: "lte", value: 3 },
|
|
44
|
+
{ field: "priceVsSma200Percent", op: "gt", value: 0 },
|
|
45
|
+
], { field: "macdBullishCrossDays", direction: "asc" }),
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
export const screenIdea = (id: string | undefined): ScreenIdea | undefined =>
|
|
49
|
+
SCREEN_IDEAS.find((entry) => entry.id === id);
|
|
@@ -4,9 +4,12 @@ import {
|
|
|
4
4
|
HIRING_APP_FIELDS,
|
|
5
5
|
CATEGORY_FIELDS,
|
|
6
6
|
NUMERIC_FIELDS,
|
|
7
|
+
OPTION_VOLATILITY_FIELDS,
|
|
8
|
+
OWNERSHIP_FIELDS,
|
|
7
9
|
RESEARCH_FIELDS,
|
|
8
10
|
KPI_FIELDS,
|
|
9
11
|
SOCIAL_FIELDS,
|
|
12
|
+
TECHNICAL_FIELDS,
|
|
10
13
|
VOLATILITY_FIELDS,
|
|
11
14
|
type NumericField,
|
|
12
15
|
type ScreenMetric,
|
|
@@ -22,6 +25,7 @@ import { canonicalExchange } from "../../../utils/exchanges";
|
|
|
22
25
|
import { LATEST_DISTRIBUTION, TRAILING_DIVIDEND_BASIS } from "../../../utils/forward-dividend";
|
|
23
26
|
import { formatCompact, formatNumber } from "../../../utils/format";
|
|
24
27
|
import { formatPriceEarnings, NOT_MEANINGFUL } from "../../../utils/price-earnings";
|
|
28
|
+
import { ATTENTION_LISTED } from "../attention/listing";
|
|
25
29
|
import { kpiFreeLimitNote } from "../company-kpis/model";
|
|
26
30
|
|
|
27
31
|
/**
|
|
@@ -90,6 +94,20 @@ export const SHORT_LABELS: Record<NumericField, string> = {
|
|
|
90
94
|
fromHigh52WPercent: "VS 52W HI%",
|
|
91
95
|
beta: "BETA",
|
|
92
96
|
atrPercent: "ATR%",
|
|
97
|
+
rsi14: "RSI 14",
|
|
98
|
+
priceVsSma20Percent: "VS SMA20%",
|
|
99
|
+
priceVsSma50Percent: "VS SMA50%",
|
|
100
|
+
priceVsSma200Percent: "VS SMA200%",
|
|
101
|
+
// Sessions since the event: 0 is the last completed session.
|
|
102
|
+
macdBullishCrossDays: "MACD BULL AGO",
|
|
103
|
+
macdBearishCrossDays: "MACD BEAR AGO",
|
|
104
|
+
goldenCrossDays: "GOLDEN X AGO",
|
|
105
|
+
deathCrossDays: "DEATH X AGO",
|
|
106
|
+
newHigh20dDays: "20D HIGH AGO",
|
|
107
|
+
newLow20dDays: "20D LOW AGO",
|
|
108
|
+
newHigh52wDays: "52W HIGH AGO",
|
|
109
|
+
newLow52wDays: "52W LOW AGO",
|
|
110
|
+
bollingerWidthPercentile: "BB WIDTH PCTL",
|
|
93
111
|
priceToBook: "P/B",
|
|
94
112
|
evToEbitda: "EV/EBITDA",
|
|
95
113
|
roePercent: "FY ROE%",
|
|
@@ -97,6 +115,17 @@ export const SHORT_LABELS: Record<NumericField, string> = {
|
|
|
97
115
|
analystUpsidePercent: "UPSIDE%",
|
|
98
116
|
ivRank: "IV RANK",
|
|
99
117
|
ivToHv: "IV/HV",
|
|
118
|
+
iv30Percent: "IV30%",
|
|
119
|
+
ivPercentile: "IV PCTL",
|
|
120
|
+
// Volatility points: IV30 25% to 27% is +2.
|
|
121
|
+
iv30Change1d: "IV30 CHG 1D",
|
|
122
|
+
iv30Change5d: "IV30 CHG 5D",
|
|
123
|
+
hv20Percent: "HV20%",
|
|
124
|
+
hvRank: "HV RANK",
|
|
125
|
+
sharesOutstanding: "SHARES OUT",
|
|
126
|
+
floatShares: "FLOAT",
|
|
127
|
+
shortPercentOfFloat: "SI % FLOAT",
|
|
128
|
+
institutionalOwnershipPercent: "INST OWN%",
|
|
100
129
|
hiringOpenRoles: "OPEN ROLES",
|
|
101
130
|
hiringNetAddsWeek: "NET ROLES/WK",
|
|
102
131
|
hiringRemotePercent: "REMOTE%",
|
|
@@ -106,7 +135,7 @@ export const SHORT_LABELS: Record<NumericField, string> = {
|
|
|
106
135
|
appRatingDrift7d: "RATING 7D",
|
|
107
136
|
appRatingCountGrowth7d: "RATINGS 7D",
|
|
108
137
|
};
|
|
109
|
-
const COMPACT = new Set<NumericField>(["kpiArr", "kpiRpo", "kpiCrpo", "kpiSubscribers", "kpiDau", "kpiMau", "kpiGmv", "kpiStores", "kpiBacklog", "marketCap", "volume", "averageVolume20d", "shortInterestShares"]);
|
|
138
|
+
const COMPACT = new Set<NumericField>(["kpiArr", "kpiRpo", "kpiCrpo", "kpiSubscribers", "kpiDau", "kpiMau", "kpiGmv", "kpiStores", "kpiBacklog", "marketCap", "volume", "averageVolume20d", "shortInterestShares", "sharesOutstanding", "floatShares"]);
|
|
110
139
|
/** Daily counts: whole numbers below a thousand, compact above. */
|
|
111
140
|
const DAILY_COUNTS = new Set<NumericField>(["xPostsPerDay", "wikiViewsPerDay"]);
|
|
112
141
|
const RATIOS = new Set<NumericField>(["kpiBookToBill", "xPostsVsMedian", "wikiViewsVsMedian", "priceToBook", "evToEbitda", "ivToHv"]);
|
|
@@ -116,10 +145,15 @@ const SIGNED = new Set<NumericField>(["hiringNetAddsWeek", "hiringZScore", "appR
|
|
|
116
145
|
"changePercent", "revenueGrowthPercent", "earningsGrowthPercent", "shortInterestChangePercent",
|
|
117
146
|
"dividendGrowth1YPercent", "dividendGrowth3YPercent",
|
|
118
147
|
...RETURNS, "fromHigh52WPercent", "epsRevision30dPercent", "analystUpsidePercent",
|
|
148
|
+
"priceVsSma20Percent", "priceVsSma50Percent", "priceVsSma200Percent",
|
|
149
|
+
"iv30Change1d", "iv30Change5d",
|
|
119
150
|
]);
|
|
120
151
|
/** Moves whose sign is the news: they take the sign colour. */
|
|
121
|
-
export const SIGN_COLORED = new Set<NumericField>(["changePercent", ...RETURNS, "epsRevision30dPercent", "analystUpsidePercent"
|
|
122
|
-
|
|
152
|
+
export const SIGN_COLORED = new Set<NumericField>(["changePercent", ...RETURNS, "epsRevision30dPercent", "analystUpsidePercent",
|
|
153
|
+
"priceVsSma20Percent", "priceVsSma50Percent", "priceVsSma200Percent"]);
|
|
154
|
+
const COUNTS = new Set<NumericField>(["appRatingCountGrowth7d", "hiringNetAddsWeek", "hiringOpenRoles", "attentionResearchUnits", "insiderPurchases90d", "insiderSales90d", "institutionalHolders", "institutionalNewHolders", "institutionalExits", "ivRank", "ivPercentile", "hvRank",
|
|
155
|
+
"macdBullishCrossDays", "macdBearishCrossDays", "goldenCrossDays", "deathCrossDays",
|
|
156
|
+
"newHigh20dDays", "newLow20dDays", "newHigh52wDays", "newLow52wDays", "bollingerWidthPercentile"]);
|
|
123
157
|
const MONETARY = new Set<NumericField>(["price", "marketCap", "kpiArr", "kpiRpo", "kpiCrpo", "kpiGmv", "kpiBacklog"]);
|
|
124
158
|
/** Shown after the screen's own criteria when the pane has room. */
|
|
125
159
|
const CONTEXT_FIELDS: NumericField[] = ["marketCap", "price", "changePercent", "trailingPE", "revenueGrowthPercent", "operatingMarginPercent", "dividendYieldPercent"];
|
|
@@ -161,13 +195,30 @@ export function metricDate(metric: ScreenMetric): { text: string; collected: boo
|
|
|
161
195
|
return { text: "--", collected: false };
|
|
162
196
|
}
|
|
163
197
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
198
|
+
const isNumeric = (field: string): field is NumericField => (NUMERIC_FIELDS as readonly string[]).includes(field);
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Focus metric, the other metrics asked for, the field the rows are sorted
|
|
202
|
+
* by (so their order is explained by a column), then the screen's numeric
|
|
203
|
+
* criteria and the context columns. Money needs one currency.
|
|
204
|
+
*/
|
|
205
|
+
export function resultFields(definition: ScreenDefinition, metric: NumericField, others: readonly NumericField[] = []): NumericField[] {
|
|
206
|
+
const sort = isNumeric(definition.sort.field) ? [definition.sort.field] : [];
|
|
207
|
+
const fields = [metric, ...others, ...sort, ...definition.criteria.map((criterion) => criterion.field).filter(isNumeric), ...CONTEXT_FIELDS];
|
|
168
208
|
return [...new Set(fields)].filter((field) => definition.currency || !MONETARY.has(field) || field === metric);
|
|
169
209
|
}
|
|
170
210
|
|
|
211
|
+
/**
|
|
212
|
+
* The metrics a pane's `metric` setting names, the focus first: one field, or
|
|
213
|
+
* the comma list `--metric` leaves (`trailingPE,dividendYieldPercent`).
|
|
214
|
+
* Unknown names are skipped; none known leaves `fallback`.
|
|
215
|
+
*/
|
|
216
|
+
export function screenMetricSetting(setting: unknown, fallback: NumericField): [NumericField, ...NumericField[]] {
|
|
217
|
+
const fields = typeof setting === "string" ? setting.split(",").map((part) => part.trim()).filter(isNumeric) : [];
|
|
218
|
+
const unique = [...new Set(fields)];
|
|
219
|
+
return unique.length > 0 ? unique as [NumericField, ...NumericField[]] : [fallback];
|
|
220
|
+
}
|
|
221
|
+
|
|
171
222
|
/**
|
|
172
223
|
* The close the screen's YTD returns are measured from, `31 Dec`: NYSE's last
|
|
173
224
|
* session of the year before the newest session a row's YTD ends on, the
|
|
@@ -288,6 +339,19 @@ export const screenLabel = (field: string) =>
|
|
|
288
339
|
fromHigh52WPercent: "From 52-week high %",
|
|
289
340
|
beta: "Beta",
|
|
290
341
|
atrPercent: "ATR%",
|
|
342
|
+
rsi14: "RSI 14",
|
|
343
|
+
priceVsSma20Percent: "Price vs SMA 20 %",
|
|
344
|
+
priceVsSma50Percent: "Price vs SMA 50 %",
|
|
345
|
+
priceVsSma200Percent: "Price vs SMA 200 %",
|
|
346
|
+
macdBullishCrossDays: "Days since MACD bullish cross",
|
|
347
|
+
macdBearishCrossDays: "Days since MACD bearish cross",
|
|
348
|
+
goldenCrossDays: "Days since golden cross",
|
|
349
|
+
deathCrossDays: "Days since death cross",
|
|
350
|
+
newHigh20dDays: "Days since new 20-day high",
|
|
351
|
+
newLow20dDays: "Days since new 20-day low",
|
|
352
|
+
newHigh52wDays: "Days since new 52-week high",
|
|
353
|
+
newLow52wDays: "Days since new 52-week low",
|
|
354
|
+
bollingerWidthPercentile: "Bollinger width percentile",
|
|
291
355
|
priceToBook: "P/B",
|
|
292
356
|
evToEbitda: "EV / EBITDA",
|
|
293
357
|
roePercent: "Annual ROE %",
|
|
@@ -295,6 +359,16 @@ export const screenLabel = (field: string) =>
|
|
|
295
359
|
analystUpsidePercent: "Analyst upside %",
|
|
296
360
|
ivRank: "IV rank",
|
|
297
361
|
ivToHv: "IV / HV",
|
|
362
|
+
iv30Percent: "IV30 %",
|
|
363
|
+
ivPercentile: "IV percentile",
|
|
364
|
+
iv30Change1d: "IV30 change 1D, vol pts",
|
|
365
|
+
iv30Change5d: "IV30 change 5D, vol pts",
|
|
366
|
+
hv20Percent: "HV20 %",
|
|
367
|
+
hvRank: "HV rank",
|
|
368
|
+
sharesOutstanding: "Shares outstanding",
|
|
369
|
+
floatShares: "Float",
|
|
370
|
+
shortPercentOfFloat: "Short interest % of float",
|
|
371
|
+
institutionalOwnershipPercent: "Institutional ownership % (13F)",
|
|
298
372
|
hiringOpenRoles: "Hiring open roles",
|
|
299
373
|
hiringNetAddsWeek: "Net open roles per week",
|
|
300
374
|
hiringRemotePercent: "Remote roles %",
|
|
@@ -538,6 +612,21 @@ const unavailableMetric = (): ScreenMetric => ({
|
|
|
538
612
|
state: "unavailable", scope: "reported", reason: "Not provided by this server", sourceUrl: null,
|
|
539
613
|
percentile: { value: null, sampleCount: 0, scope: "covered-universe" },
|
|
540
614
|
});
|
|
615
|
+
/**
|
|
616
|
+
* The fields the field and metric pickers offer. ATTN's two (ATTN HRS,
|
|
617
|
+
* ATTN Z) stay out until ATTN is listed again (attention/listing.ts), but for
|
|
618
|
+
* one the screen already uses, so a saved screen still shows it.
|
|
619
|
+
*/
|
|
620
|
+
export function pickerFields<Field extends { id: string }>(
|
|
621
|
+
fields: readonly Field[],
|
|
622
|
+
inUse: readonly string[] = [],
|
|
623
|
+
listed: boolean = ATTENTION_LISTED,
|
|
624
|
+
): Field[] {
|
|
625
|
+
if (listed) return [...fields];
|
|
626
|
+
const unlisted = new Set<string>(ATTENTION_FIELDS);
|
|
627
|
+
return fields.filter((field) => !unlisted.has(field.id) || inUse.includes(field.id));
|
|
628
|
+
}
|
|
629
|
+
|
|
541
630
|
/** Company KPIs, guidance, hiring and app attention: the server leaves them out of a screen for a caller without Gloom Pro. */
|
|
542
631
|
const PRO_FIELDS: ReadonlySet<string> = new Set([...KPI_FIELDS, ...HIRING_APP_FIELDS]);
|
|
543
632
|
const withheldMetric = (): ScreenMetric => ({ ...unavailableMetric(), reason: "Needs Gloom Pro", access: "pro" });
|
|
@@ -593,9 +682,9 @@ export function validateScreenPayload(value: unknown): ScreenPayload {
|
|
|
593
682
|
throw new Error("Invalid screener listing identity.");
|
|
594
683
|
ids.add(screenRowId(row));
|
|
595
684
|
for (const field of NUMERIC_FIELDS) {
|
|
596
|
-
// A server from before social, research, volatility
|
|
597
|
-
// A Pro field it left out was held back from this caller.
|
|
598
|
-
if (!row.metrics[field] && ([...SOCIAL_FIELDS, ...RESEARCH_FIELDS, ...VOLATILITY_FIELDS, ...ATTENTION_FIELDS, ...HIRING_APP_FIELDS, ...KPI_FIELDS, ...DIVIDEND_FIELDS] as readonly string[]).includes(field))
|
|
685
|
+
// A server from before social, research, volatility, dividend, technical, option volatility or ownership fields
|
|
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))
|
|
599
688
|
row.metrics[field] = PRO_FIELDS.has(field) ? withheldMetric() : unavailableMetric();
|
|
600
689
|
const metric = row.metrics[field];
|
|
601
690
|
if (
|