@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
|
@@ -282,7 +282,7 @@ export function calculateKellySizing({
|
|
|
282
282
|
price?: number | null;
|
|
283
283
|
}): KellySizingResult {
|
|
284
284
|
const warnings: string[] = [];
|
|
285
|
-
if (!finite(
|
|
285
|
+
if (!finite(currentValue)) {
|
|
286
286
|
return {
|
|
287
287
|
mode,
|
|
288
288
|
bankroll: 0,
|
|
@@ -301,7 +301,7 @@ export function calculateKellySizing({
|
|
|
301
301
|
riskFraction: 0,
|
|
302
302
|
expectedReturn: 0,
|
|
303
303
|
expectedLogGrowth: 0,
|
|
304
|
-
warnings: [
|
|
304
|
+
warnings: ["Current holding value unavailable; check FX rates."],
|
|
305
305
|
clipReasons: [],
|
|
306
306
|
recommendationLabel: "Unavailable",
|
|
307
307
|
};
|
|
@@ -342,20 +342,24 @@ export function calculateKellySizing({
|
|
|
342
342
|
|
|
343
343
|
const safeCurrentValue = finite(currentValue) ? Math.max(0, currentValue) : 0;
|
|
344
344
|
const safePrice = price != null && finite(price) && price > 0 ? price : null;
|
|
345
|
-
|
|
346
|
-
const
|
|
347
|
-
const
|
|
345
|
+
// Sizes are shares of the bankroll; only the money amounts need one.
|
|
346
|
+
const hasBankroll = finite(bankroll) && bankroll > 0;
|
|
347
|
+
const targetValue = hasBankroll ? clippedFraction * bankroll : null;
|
|
348
|
+
const addTrimValue = targetValue == null ? null : targetValue - safeCurrentValue;
|
|
349
|
+
const estimatedUnits = safePrice && addTrimValue != null ? addTrimValue / safePrice : null;
|
|
348
350
|
const riskFraction = clippedFraction * downsideLossFraction;
|
|
349
|
-
const riskValue = riskFraction * bankroll;
|
|
351
|
+
const riskValue = hasBankroll ? riskFraction * bankroll : null;
|
|
352
|
+
// Nothing held is 0% of any bankroll; a holding with no bankroll has no share.
|
|
353
|
+
const currentFraction = hasBankroll ? safeCurrentValue / bankroll : safeCurrentValue > 0 ? null : 0;
|
|
350
354
|
|
|
351
355
|
if (fullKellyFraction === 0 && solve.expectedReturn <= 0) warnings.push("No positive Kelly edge.");
|
|
352
356
|
if (downsideLossFraction <= 0) warnings.push("No downside assumption; risk caps cannot bind.");
|
|
353
357
|
|
|
354
358
|
return {
|
|
355
359
|
mode,
|
|
356
|
-
bankroll,
|
|
360
|
+
bankroll: hasBankroll ? bankroll : 0,
|
|
357
361
|
currentValue: safeCurrentValue,
|
|
358
|
-
currentFraction
|
|
362
|
+
currentFraction,
|
|
359
363
|
price: safePrice,
|
|
360
364
|
fullKellyFraction,
|
|
361
365
|
fractionalKellyFraction,
|
|
@@ -372,7 +372,7 @@ export function KellySizerPane({ focused, width, height }: PaneProps) {
|
|
|
372
372
|
const sensitivity = useMemo(() => buildSensitivityGrid(sizingMode, activeDraft), [activeDraft, sizingMode]);
|
|
373
373
|
const curveMaxFraction = useMemo(
|
|
374
374
|
() => getKellyCurveMaxFraction(sizingMode, activeDraft, [
|
|
375
|
-
result.currentFraction,
|
|
375
|
+
result.currentFraction ?? 0,
|
|
376
376
|
result.clippedFraction,
|
|
377
377
|
result.fullKellyFraction,
|
|
378
378
|
]),
|
|
@@ -384,7 +384,9 @@ export function KellySizerPane({ focused, width, height }: PaneProps) {
|
|
|
384
384
|
);
|
|
385
385
|
const curveXAxisLabels = useMemo(() => buildKellyCurveXAxisLabels(curveMaxFraction), [curveMaxFraction]);
|
|
386
386
|
const currentGrowth = useMemo(
|
|
387
|
-
() =>
|
|
387
|
+
() => result.currentFraction == null
|
|
388
|
+
? null
|
|
389
|
+
: calculateExpectedLogGrowthAtFraction(sizingMode, activeDraft, result.currentFraction),
|
|
388
390
|
[activeDraft, sizingMode, result.currentFraction],
|
|
389
391
|
);
|
|
390
392
|
const targetGrowth = useMemo(
|
|
@@ -393,15 +395,16 @@ export function KellySizerPane({ focused, width, height }: PaneProps) {
|
|
|
393
395
|
);
|
|
394
396
|
const curveMarkers = useMemo<StaticChartXMarker[]>(() => {
|
|
395
397
|
if (!Number.isFinite(curveMaxFraction) || curveMaxFraction <= 0) return [];
|
|
396
|
-
|
|
398
|
+
// Caps apply in turn, so with both binding the last one set the target.
|
|
399
|
+
const targetLabel = result.clipReasons.at(-1)?.replace(/^max /, "") ?? "target";
|
|
397
400
|
const markers: StaticChartXMarker[] = [
|
|
398
|
-
{
|
|
401
|
+
...(result.currentFraction == null ? [] : [{
|
|
399
402
|
id: "current",
|
|
400
403
|
xRatio: result.currentFraction / curveMaxFraction,
|
|
401
404
|
label: "current",
|
|
402
405
|
color: colors.textDim,
|
|
403
406
|
lineChar: "┊",
|
|
404
|
-
},
|
|
407
|
+
}]),
|
|
405
408
|
{
|
|
406
409
|
id: "target",
|
|
407
410
|
xRatio: result.clippedFraction / curveMaxFraction,
|
|
@@ -474,9 +477,6 @@ export function KellySizerPane({ focused, width, height }: PaneProps) {
|
|
|
474
477
|
usePaneFooter(KELLY_PANE_ID, () => ({
|
|
475
478
|
info: historyStatus
|
|
476
479
|
? [{ id: "history", parts: [historyStatus] }]
|
|
477
|
-
: bankroll <= 0 && ticker
|
|
478
|
-
// Every size reads 0% until there is a bankroll; say so where status lives.
|
|
479
|
-
? [{ id: "bankroll", parts: [{ text: "no bankroll", tone: "warning" as const }] }]
|
|
480
480
|
: result.warnings.length > 0
|
|
481
481
|
? [{ id: "warning", parts: [{ text: result.warnings[0]!, tone: "warning" as const }] }]
|
|
482
482
|
: result.clipReasons.length > 0
|
|
@@ -488,7 +488,7 @@ export function KellySizerPane({ focused, width, height }: PaneProps) {
|
|
|
488
488
|
{ id: "search", key: "/", label: "search", onPress: focusTickerSearch },
|
|
489
489
|
...(viewInBar ? [] : [{ id: "sensitivity", key: "s", label: showSensitivity ? "ensitivity off" : "ensitivity", onPress: toggleSensitivity }]),
|
|
490
490
|
],
|
|
491
|
-
}), [activeInputId,
|
|
491
|
+
}), [activeInputId, editSelectedField, focusTickerSearch, historyStatus?.text, historyStatus?.tone, result.clipReasons, result.warnings, selectedField?.id, selectedField?.label, showSensitivity, ticker, toggleSensitivity, viewInBar]);
|
|
492
492
|
|
|
493
493
|
const portfolioTabs = useMemo(
|
|
494
494
|
() => config.portfolios.map((portfolio) => ({ label: portfolio.name, value: portfolio.id })),
|
|
@@ -11,7 +11,7 @@ import { resolveChartPalette } from "../../../components/chart/core/palette";
|
|
|
11
11
|
import type { StaticChartXMarker } from "../../../components/chart/static";
|
|
12
12
|
import { colors, priceColor } from "../../../theme/colors";
|
|
13
13
|
import { Box } from "../../../ui";
|
|
14
|
-
import { displayWidth, formatCurrency, formatNumber } from "../../../utils/format";
|
|
14
|
+
import { displayWidth, formatCurrency, formatNumber, MISSING_VALUE } from "../../../utils/format";
|
|
15
15
|
import type { KellySizingResult, SensitivityGrid } from "./types";
|
|
16
16
|
import type { KellyHistoryInputs } from "./history";
|
|
17
17
|
import { formatPct, formatSignedPct } from "./view";
|
|
@@ -32,10 +32,15 @@ export function buildKellyHistoryItems(inputs: KellyHistoryInputs): StatItem[] {
|
|
|
32
32
|
];
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/** What the first money figure says while there is no bankroll to size in. */
|
|
36
|
+
const BANKROLL_HINT = "Set a bankroll";
|
|
37
|
+
|
|
35
38
|
/**
|
|
36
39
|
* The sizing result as one band under the inputs, laid out in the FieldGrid's
|
|
37
40
|
* columns: the Kelly fractions, then the move in money, then risk, then log
|
|
38
|
-
* growth at full Kelly, at the current size and at the target.
|
|
41
|
+
* growth at full Kelly, at the current size and at the target. Money figures
|
|
42
|
+
* read a dash until there is a bankroll; the fractions and growth never wait
|
|
43
|
+
* for one.
|
|
39
44
|
*/
|
|
40
45
|
export function buildKellyResultItems({
|
|
41
46
|
result,
|
|
@@ -47,12 +52,13 @@ export function buildKellyResultItems({
|
|
|
47
52
|
}: {
|
|
48
53
|
result: KellySizingResult;
|
|
49
54
|
baseCurrency: string;
|
|
50
|
-
|
|
55
|
+
/** Null when the holding is not a share of any bankroll. */
|
|
56
|
+
currentGrowth: number | null;
|
|
51
57
|
targetGrowth: number;
|
|
52
58
|
width: number;
|
|
53
59
|
columns: number;
|
|
54
60
|
}): StatItem[] {
|
|
55
|
-
const addTrim = formatCurrency(result.addTrimValue, baseCurrency);
|
|
61
|
+
const addTrim = result.addTrimValue == null ? MISSING_VALUE : formatCurrency(result.addTrimValue, baseCurrency);
|
|
56
62
|
const units = result.estimatedUnits == null ? null : `${formatNumber(result.estimatedUnits, 1)} units`;
|
|
57
63
|
// The units ride beside the amount when the column has room for label,
|
|
58
64
|
// amount and units (plus the cell inset and gaps); otherwise they take a
|
|
@@ -68,14 +74,16 @@ export function buildKellyResultItems({
|
|
|
68
74
|
value: formatPct(result.clippedFraction, 2),
|
|
69
75
|
tone: result.clipReasons.length > 0 ? "positive" : undefined,
|
|
70
76
|
},
|
|
71
|
-
{ id: "current", label: "Current %", value: formatPct(result.currentFraction, 1) },
|
|
72
|
-
|
|
77
|
+
{ id: "current", label: "Current %", value: result.currentFraction == null ? MISSING_VALUE : formatPct(result.currentFraction, 1) },
|
|
78
|
+
result.targetValue == null
|
|
79
|
+
? { id: "target", label: "Target val", value: MISSING_VALUE, detail: BANKROLL_HINT }
|
|
80
|
+
: { id: "target", label: "Target val", value: formatCurrency(result.targetValue, baseCurrency) },
|
|
73
81
|
{
|
|
74
82
|
id: "addTrim",
|
|
75
83
|
label: "Add / trim",
|
|
76
84
|
value: addTrim,
|
|
77
85
|
detail: unitsInline ? units ?? undefined : undefined,
|
|
78
|
-
color: priceColor(result.addTrimValue),
|
|
86
|
+
color: result.addTrimValue == null ? undefined : priceColor(result.addTrimValue),
|
|
79
87
|
},
|
|
80
88
|
...(units != null && !unitsInline
|
|
81
89
|
? [{ id: "units", label: "Units", value: formatNumber(result.estimatedUnits!, 1) }]
|
|
@@ -83,9 +91,10 @@ export function buildKellyResultItems({
|
|
|
83
91
|
{
|
|
84
92
|
id: "risk",
|
|
85
93
|
label: "Risk",
|
|
86
|
-
value: formatCurrency(result.riskValue, baseCurrency),
|
|
94
|
+
value: result.riskValue == null ? MISSING_VALUE : formatCurrency(result.riskValue, baseCurrency),
|
|
87
95
|
detail: formatPct(result.riskFraction, 2),
|
|
88
|
-
|
|
96
|
+
// Nothing at risk is not a loss.
|
|
97
|
+
tone: result.riskValue != null && result.riskValue > 0 ? "negative" : undefined,
|
|
89
98
|
},
|
|
90
99
|
{ id: "worstLoss", label: "Worst loss", value: formatPct(result.downsideLossFraction, 1) },
|
|
91
100
|
{
|
|
@@ -95,7 +104,7 @@ export function buildKellyResultItems({
|
|
|
95
104
|
color: priceColor(result.expectedReturn),
|
|
96
105
|
},
|
|
97
106
|
{ id: "fullGrowth", label: "Full growth", value: formatSignedPct(result.expectedLogGrowth) },
|
|
98
|
-
{ id: "growthNow", label: "Growth now", value: formatSignedPct(currentGrowth) },
|
|
107
|
+
{ id: "growthNow", label: "Growth now", value: currentGrowth == null ? MISSING_VALUE : formatSignedPct(currentGrowth) },
|
|
99
108
|
{ id: "growthTarget", label: "Growth tgt", value: formatSignedPct(targetGrowth) },
|
|
100
109
|
];
|
|
101
110
|
}
|
|
@@ -69,25 +69,31 @@ export interface KellySolveResult {
|
|
|
69
69
|
warning?: string;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* The fractions, expected return and growth need no bankroll. The money fields
|
|
74
|
+
* (target, add or trim, units, risk) are null until there is one, and
|
|
75
|
+
* `currentFraction` is null for a holding that has no bankroll to be a share of.
|
|
76
|
+
*/
|
|
72
77
|
export interface KellySizingResult {
|
|
73
78
|
mode: KellySizingMode;
|
|
74
79
|
bankroll: number;
|
|
75
80
|
currentValue: number;
|
|
76
|
-
currentFraction: number;
|
|
81
|
+
currentFraction: number | null;
|
|
77
82
|
price: number | null;
|
|
78
83
|
fullKellyFraction: number;
|
|
79
84
|
fractionalKellyFraction: number;
|
|
80
85
|
unclippedFraction: number;
|
|
81
86
|
clippedFraction: number;
|
|
82
|
-
targetValue: number;
|
|
83
|
-
addTrimValue: number;
|
|
87
|
+
targetValue: number | null;
|
|
88
|
+
addTrimValue: number | null;
|
|
84
89
|
estimatedUnits: number | null;
|
|
85
90
|
downsideLossFraction: number;
|
|
86
|
-
riskValue: number;
|
|
91
|
+
riskValue: number | null;
|
|
87
92
|
riskFraction: number;
|
|
88
93
|
expectedReturn: number;
|
|
89
94
|
expectedLogGrowth: number;
|
|
90
95
|
warnings: string[];
|
|
96
|
+
/** The caps that cut the size, in the order applied; the last one set the target. */
|
|
91
97
|
clipReasons: string[];
|
|
92
98
|
recommendationLabel: string;
|
|
93
99
|
}
|
|
@@ -5,9 +5,9 @@ export const MACRO_RELEASE_TABLE: {
|
|
|
5
5
|
coveredThrough: string;
|
|
6
6
|
days: Record<"cpi" | "jobs" | "fomc", Record<number, readonly string[]>>;
|
|
7
7
|
} = {
|
|
8
|
-
checkedAt: "2026-10-
|
|
8
|
+
checkedAt: "2026-10-28",
|
|
9
9
|
/** The last day the lists are complete for: later sessions are left out, release day or not. */
|
|
10
|
-
coveredThrough: "2026-10-
|
|
10
|
+
coveredThrough: "2026-10-27",
|
|
11
11
|
days: {
|
|
12
12
|
// BLS Consumer Price Index, 8:30 ET.
|
|
13
13
|
cpi: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { apiClient } from "../../../api-client";
|
|
2
|
+
import { createTtlCache } from "../../../data/ttl-cache";
|
|
2
3
|
import type { MarketHeatmapResult, MarketHeatmapUniverseId } from "../../../api-client/market-discovery";
|
|
3
4
|
export type { MarketHeatmapUniverseId } from "../../../api-client/market-discovery";
|
|
4
5
|
|
|
@@ -39,8 +40,7 @@ const MAX_MARKET_HEATMAP_COUNT = 500;
|
|
|
39
40
|
export function heatmapCheckIntervalMs(board: Pick<MarketHeatmapResult, "access"> | null | undefined): number {
|
|
40
41
|
return board?.access === "realtime" ? 15_000 : 60_000;
|
|
41
42
|
}
|
|
42
|
-
const
|
|
43
|
-
const memoryCache = new Map<string, { expiresAt: number; result: MarketHeatmapBoard }>();
|
|
43
|
+
const boards = createTtlCache<MarketHeatmapBoard>({ ttlMs: heatmapCheckIntervalMs });
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* One tile per symbol. A company listed on two exchanges (KHC on NASDAQ and
|
|
@@ -75,43 +75,18 @@ async function loadMarketHeatmap(universe: MarketHeatmapUniverseId, count: numbe
|
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
export
|
|
78
|
+
export function fetchMarketHeatmap(
|
|
79
79
|
universe: MarketHeatmapUniverseId,
|
|
80
80
|
options?: MarketHeatmapFetchOptions,
|
|
81
81
|
sources?: MarketHeatmapSources,
|
|
82
82
|
): Promise<MarketHeatmapBoard> {
|
|
83
83
|
const count = Math.max(1, Math.min(MAX_MARKET_HEATMAP_COUNT, Math.round(options?.count ?? DEFAULT_COUNT)));
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (useCache && !options?.forceRefresh && cached && cached.expiresAt > now) {
|
|
89
|
-
return cached.result;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (useCache) {
|
|
93
|
-
const active = activeFetches.get(cacheKey);
|
|
94
|
-
if (active) return active;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const fetchPromise = loadMarketHeatmap(universe, count, sources)
|
|
98
|
-
.then((result) => {
|
|
99
|
-
if (useCache) {
|
|
100
|
-
memoryCache.set(cacheKey, { result, expiresAt: Date.now() + heatmapCheckIntervalMs(result) });
|
|
101
|
-
}
|
|
102
|
-
return result;
|
|
103
|
-
})
|
|
104
|
-
.finally(() => {
|
|
105
|
-
if (activeFetches.get(cacheKey) === fetchPromise) {
|
|
106
|
-
activeFetches.delete(cacheKey);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
if (useCache) activeFetches.set(cacheKey, fetchPromise);
|
|
111
|
-
return fetchPromise;
|
|
84
|
+
const load = () => loadMarketHeatmap(universe, count, sources);
|
|
85
|
+
// A board asked for uncached, or through another client, neither reads nor fills the shared cache.
|
|
86
|
+
if (options?.cache === false || sources?.client) return load();
|
|
87
|
+
return boards.load(`${universe}:${count}`, load, { force: options?.forceRefresh });
|
|
112
88
|
}
|
|
113
89
|
|
|
114
90
|
export function resetMarketHeatmapCache(): void {
|
|
115
|
-
|
|
116
|
-
memoryCache.clear();
|
|
91
|
+
boards.clear();
|
|
117
92
|
}
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type PreferredMoversOptions,
|
|
25
25
|
type TrendingSymbol,
|
|
26
26
|
} from "./screener";
|
|
27
|
+
import { meetsMinFloat } from "./min-float";
|
|
27
28
|
|
|
28
29
|
export interface MarketMoverTabResult extends MarketMoversResult {
|
|
29
30
|
tab: ScreenerTabId;
|
|
@@ -100,10 +101,14 @@ export function marketMoverQuoteError(result: Pick<MarketMoverTabResult, "unavai
|
|
|
100
101
|
export async function loadMarketMoverTab(
|
|
101
102
|
tab: ScreenerTabId,
|
|
102
103
|
provider: DataProvider | null,
|
|
103
|
-
options?: Pick<PreferredMoversOptions, "forceRefresh" | "metadataWaitMs"> & {
|
|
104
|
+
options?: Pick<PreferredMoversOptions, "forceRefresh" | "metadataWaitMs"> & {
|
|
105
|
+
basis?: MoverRankBasis;
|
|
106
|
+
/** Keep the rows with a float of at least this many shares. Trending carries no float and ignores it. */
|
|
107
|
+
minFloat?: number | null;
|
|
108
|
+
},
|
|
104
109
|
dependencies: MarketMoverTabDependencies = defaultDependencies,
|
|
105
110
|
): Promise<MarketMoverTabResult> {
|
|
106
|
-
const { basis, ...fetchOptions } = options ?? {};
|
|
111
|
+
const { basis, minFloat = null, ...fetchOptions } = options ?? {};
|
|
107
112
|
if (tab === "trending") {
|
|
108
113
|
const trending = await dependencies.fetchTrending(LIST_ROWS, fetchOptions);
|
|
109
114
|
const hydrated = await hydrateTrending(trending.data, provider);
|
|
@@ -116,13 +121,15 @@ export async function loadMarketMoverTab(
|
|
|
116
121
|
}
|
|
117
122
|
const category = CATEGORY_MAP[tab];
|
|
118
123
|
// The server picks the candidates on the basis the list ranks by, so after the close they are the session's movers.
|
|
119
|
-
|
|
124
|
+
// A minimum float drops rows, so Most Active then reads as many candidates as the price lists.
|
|
125
|
+
const result = await dependencies.fetchPreferred(category, category === "most_actives" && minFloat == null ? LIST_ROWS : MOVER_CANDIDATES, {
|
|
120
126
|
...fetchOptions,
|
|
121
127
|
session: basis ?? "regular",
|
|
122
128
|
});
|
|
123
129
|
// Each row read as `ticker` reads it, then ranked by the move its Chg% shows.
|
|
124
130
|
const { quotes, rankedBy } = rankMoverQuotes(category, result.quotes.map(moverRegularSession), basis);
|
|
125
|
-
|
|
131
|
+
const kept = quotes.filter((quote) => meetsMinFloat(quote.floatShares, minFloat));
|
|
132
|
+
return { ...result, quotes: kept.slice(0, LIST_ROWS), rankedBy, tab };
|
|
126
133
|
}
|
|
127
134
|
|
|
128
135
|
/** The server refused the list for the account's plan (gaps, without Pro). */
|
|
@@ -136,12 +143,23 @@ export class SessionMoversAccessError extends Error {
|
|
|
136
143
|
export async function loadSessionMovers(
|
|
137
144
|
view: CloudSessionMoversCategory,
|
|
138
145
|
side: CloudSessionMoversSide,
|
|
139
|
-
options?: { forceRefresh?: boolean },
|
|
146
|
+
options?: { forceRefresh?: boolean; minFloat?: number | null },
|
|
140
147
|
): Promise<CloudSessionMoversPayload> {
|
|
141
|
-
const
|
|
148
|
+
const minFloat = options?.minFloat ?? null;
|
|
149
|
+
// A minimum float drops rows, so it reads the most the server lists.
|
|
150
|
+
const response = await apiClient.getCloudMarketScreener(view, minFloat == null ? LIST_ROWS : MOVER_CANDIDATES, options?.forceRefresh ? "refresh" : "cache-first", side);
|
|
142
151
|
if (response.reasonCode === "PRO_REQUIRED") throw new SessionMoversAccessError();
|
|
143
152
|
if ((response.status === "success" || response.status === "partial") && response.data) {
|
|
144
|
-
|
|
153
|
+
const payload = response.stale ? { ...response.data, stale: true } : response.data;
|
|
154
|
+
return minFloat == null ? payload : { ...payload, items: withMinFloat(payload.items, minFloat) };
|
|
145
155
|
}
|
|
146
156
|
throw new Error("Session movers temporarily unavailable");
|
|
147
157
|
}
|
|
158
|
+
|
|
159
|
+
/** The rows with a float of at least `minFloat`, the first LIST_ROWS of them, ranked again from 1. */
|
|
160
|
+
export function withMinFloat<T extends { rank: number; floatShares: number | null }>(items: readonly T[], minFloat: number): T[] {
|
|
161
|
+
return items
|
|
162
|
+
.filter((item) => meetsMinFloat(item.floatShares, minFloat))
|
|
163
|
+
.slice(0, LIST_ROWS)
|
|
164
|
+
.map((item, index) => ({ ...item, rank: index + 1 }));
|
|
165
|
+
}
|
|
@@ -14,6 +14,7 @@ import { createRows, fiftyTwoWeekPositionPercent, formatMoverPrice, MOVER_RANKIN
|
|
|
14
14
|
import { EXTENDED_SESSION_LABELS, type ExtendedSession } from "../../../market-data/market/status";
|
|
15
15
|
import type { HeadlessPaneColumn } from "../../../types/headless";
|
|
16
16
|
import { isSessionTab, resolveSide } from "./session";
|
|
17
|
+
import { hasPartialFloat, minFloatShares, normalizeMinFloat, PARTIAL_FLOAT_NOTE } from "./min-float";
|
|
17
18
|
|
|
18
19
|
const rowPrice = (value: unknown, row: Record<string, unknown>) => formatMoverPrice(typeof value === "number" ? value : null, typeof row.currency === "string" ? row.currency : "", moverReferencePrice(row as unknown as MarketMoverRow));
|
|
19
20
|
|
|
@@ -69,15 +70,19 @@ function sessionColumns(view: CloudSessionMoversCategory) {
|
|
|
69
70
|
];
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
const FLOAT_COLUMN: HeadlessPaneColumn = { key: "floatShares", header: "Float", align: "right", format: compact };
|
|
74
|
+
|
|
72
75
|
export interface MarketMoversHeadlessDependencies {
|
|
73
76
|
load(
|
|
74
77
|
args: HeadlessPaneLoadArgs,
|
|
75
78
|
tab: ScreenerTabId,
|
|
76
79
|
provider: DataProvider,
|
|
80
|
+
minFloat: number | null,
|
|
77
81
|
): Promise<MarketMoverTabResult>;
|
|
78
82
|
loadSession(
|
|
79
83
|
view: CloudSessionMoversCategory,
|
|
80
84
|
side: CloudSessionMoversSide,
|
|
85
|
+
minFloat: number | null,
|
|
81
86
|
): Promise<CloudSessionMoversPayload>;
|
|
82
87
|
}
|
|
83
88
|
|
|
@@ -89,8 +94,8 @@ export interface MarketMoversHeadlessDependencies {
|
|
|
89
94
|
const ONE_SHOT_METADATA_WAIT_MS = 10_000;
|
|
90
95
|
|
|
91
96
|
const defaultDependencies: MarketMoversHeadlessDependencies = {
|
|
92
|
-
load: (_args, tab, provider) => loadMarketMoverTab(tab, provider, { metadataWaitMs: ONE_SHOT_METADATA_WAIT_MS }),
|
|
93
|
-
loadSession: (view, side) => loadSessionMovers(view, side),
|
|
97
|
+
load: (_args, tab, provider, minFloat) => loadMarketMoverTab(tab, provider, { metadataWaitMs: ONE_SHOT_METADATA_WAIT_MS, minFloat }),
|
|
98
|
+
loadSession: (view, side, minFloat) => loadSessionMovers(view, side, { minFloat }),
|
|
94
99
|
};
|
|
95
100
|
|
|
96
101
|
export function createMarketMoversHeadless(
|
|
@@ -128,6 +133,14 @@ export function createMarketMoversHeadless(
|
|
|
128
133
|
],
|
|
129
134
|
defaultValue: "up",
|
|
130
135
|
pluginState: { pluginId: "market-overview", key: "sessionSide" },
|
|
136
|
+
}, {
|
|
137
|
+
key: "min-float",
|
|
138
|
+
description: "Keep the rows whose float (the one EQS serves) is at least this many shares, such as 10M or 500k; any lifts it. A row with no float is left out. Trending carries no float and ignores it.",
|
|
139
|
+
type: "string",
|
|
140
|
+
placeholder: "shares",
|
|
141
|
+
example: "--min-float 10M",
|
|
142
|
+
normalize: normalizeMinFloat,
|
|
143
|
+
pluginState: { pluginId: "market-overview", key: "minFloat" },
|
|
131
144
|
}],
|
|
132
145
|
columns: COLUMNS,
|
|
133
146
|
describe: (args) => isSessionTab(String(args.options.list))
|
|
@@ -135,21 +148,26 @@ export function createMarketMoversHeadless(
|
|
|
135
148
|
: `Market Movers | ${String(args.options.list)}`,
|
|
136
149
|
async load(args, ctx) {
|
|
137
150
|
const tab = args.options.list as TabId;
|
|
151
|
+
const minFloatValue = typeof args.options["min-float"] === "string" ? args.options["min-float"] : null;
|
|
152
|
+
const minFloat = minFloatShares(minFloatValue);
|
|
138
153
|
if (isSessionTab(tab)) {
|
|
139
154
|
const side = resolveSide(tab, String(args.options.side));
|
|
140
|
-
const payload = await dependencies.loadSession(tab, side);
|
|
155
|
+
const payload = await dependencies.loadSession(tab, side, minFloat);
|
|
141
156
|
return {
|
|
142
157
|
columns: sessionColumns(tab),
|
|
143
158
|
rows: payload.items.map((item) => ({
|
|
144
159
|
...item,
|
|
145
160
|
events: item.catalysts.map((catalyst) => CATALYST_LABEL[catalyst] ?? catalyst).join(" "),
|
|
146
161
|
})),
|
|
147
|
-
|
|
162
|
+
...(hasPartialFloat(payload.items) ? { notes: [PARTIAL_FLOAT_NOTE] } : {}),
|
|
163
|
+
metadata: { list: tab, side, session: payload.session, phase: payload.phase, asOf: payload.asOf, stale: payload.stale === true, minFloat },
|
|
148
164
|
// A Pro account's list is the live board; the free one says it is delayed.
|
|
149
165
|
...(payload.access === "delayed" ? {} : { freshness: { status: "live" as const } }),
|
|
150
166
|
};
|
|
151
167
|
}
|
|
152
|
-
|
|
168
|
+
// Trending carries no float, so a minimum does not apply to it.
|
|
169
|
+
const floatFiltered = minFloat != null && tab !== "trending";
|
|
170
|
+
const result = await dependencies.load(args, tab, ctx.marketData, floatFiltered ? minFloat : null);
|
|
153
171
|
const ranked = createRows(result.quotes);
|
|
154
172
|
// Last and Change % are the regular session; an extended print is a column of its own.
|
|
155
173
|
const extendedSessions = moverExtendedSessions(ranked);
|
|
@@ -168,16 +186,18 @@ export function createMarketMoversHeadless(
|
|
|
168
186
|
// Beside an extended column, say which move the order follows.
|
|
169
187
|
const rankedBy = result.rankedBy ?? null;
|
|
170
188
|
const quoteError = marketMoverQuoteError(result);
|
|
189
|
+
const columns = [...COLUMNS.slice(0, 5), ...extendedSessions.map(extendedColumn), ...COLUMNS.slice(5), ...(floatFiltered ? [FLOAT_COLUMN] : [])];
|
|
190
|
+
const notes = [
|
|
191
|
+
...(rankedBy && rankedBy !== "volume" && extendedSessions.length > 0 ? [`Ranked by ${MOVER_RANKING_LABELS[rankedBy]}.`] : []),
|
|
192
|
+
...(minFloat != null && !floatFiltered ? ["Trending carries no float, so --min-float does not apply to it."] : []),
|
|
193
|
+
...(floatFiltered && hasPartialFloat(rows) ? [PARTIAL_FLOAT_NOTE] : []),
|
|
194
|
+
];
|
|
171
195
|
return {
|
|
172
|
-
...(
|
|
173
|
-
? { columns: [...COLUMNS.slice(0, 5), ...extendedSessions.map(extendedColumn), ...COLUMNS.slice(5)] }
|
|
174
|
-
: {}),
|
|
196
|
+
...(columns.length !== COLUMNS.length ? { columns } : {}),
|
|
175
197
|
rows,
|
|
176
198
|
...(quoteError ? { complete: false, unavailableSymbols: result.unavailableSymbols, errors: [quoteError] } : {}),
|
|
177
|
-
...(
|
|
178
|
-
|
|
179
|
-
: {}),
|
|
180
|
-
metadata: { list: tab, source: result.source, stale: result.stale, rankedBy },
|
|
199
|
+
...(notes.length > 0 ? { notes } : {}),
|
|
200
|
+
metadata: { list: tab, source: result.source, stale: result.stale, rankedBy, minFloat: floatFiltered ? minFloat : null },
|
|
181
201
|
// A Pro account's Gainers, Losers and Most Active are the real-time lists.
|
|
182
202
|
...(result.source === "cloud" ? { freshness: { status: "live" as const } } : {}),
|
|
183
203
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Box, useUiCapabilities } from "../../../ui";
|
|
2
2
|
import { nextHeaderSort } from "../../../utils/sort-values";
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { DataTableView, EmptyState, usePaneFooter, usePaneTabs, type DataTableKeyEvent } from "../../../components";
|
|
4
|
+
import { DataTableView, EmptyState, QueryBar, usePaneFooter, usePaneTabs, type DataTableKeyEvent } from "../../../components";
|
|
5
5
|
import { handleRefreshKey } from "../../../components/data-table/table-pane";
|
|
6
6
|
import type { PaneProps } from "../../../types/plugin";
|
|
7
7
|
import type { PluginModule } from "../plugin-module";
|
|
@@ -41,6 +41,7 @@ import { loadMarketMoverTab, marketMoverQuoteError } from "./client";
|
|
|
41
41
|
import { summaryFooterSegments } from "./footer";
|
|
42
42
|
import { isSessionTab, resolveActiveTab, usSessionAt, type UsSession } from "./session";
|
|
43
43
|
import { SessionMoversBody } from "./session-body";
|
|
44
|
+
import { ANY_FLOAT, minFloatEmptyTitle, minFloatFilter, minFloatShares, partialFloatFooter } from "./min-float";
|
|
44
45
|
import { marketMoversHeadless } from "./headless";
|
|
45
46
|
import { buildMarketMoverColumns, renderMarketMoverCell } from "./table";
|
|
46
47
|
import type { ExtendedSession } from "../../../market-data/market/status";
|
|
@@ -152,16 +153,21 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
152
153
|
}) {
|
|
153
154
|
const dataProvider = useAssetData();
|
|
154
155
|
const { pinTicker } = usePluginTickerActions();
|
|
156
|
+
const [minFloatValue, setMinFloatValue] = usePluginPaneState<string>("minFloat", ANY_FLOAT);
|
|
157
|
+
// Trending carries no float, so the minimum is not offered there.
|
|
158
|
+
const floatFiltered = activeTab !== "trending";
|
|
159
|
+
const minFloat = floatFiltered ? minFloatShares(minFloatValue) : null;
|
|
160
|
+
const listKey = `${activeTab}:${minFloat ?? ""}`;
|
|
155
161
|
const [quotes, setQuotes] = useState<MoverQuote[]>([]);
|
|
156
|
-
const [
|
|
157
|
-
const visibleQuotes =
|
|
162
|
+
const [loadedKey, setLoadedKey] = useState<string | null>(null);
|
|
163
|
+
const visibleQuotes = loadedKey === listKey ? quotes : NO_QUOTES;
|
|
158
164
|
// The first load starts before the effect runs; an empty board is not "no data".
|
|
159
165
|
const [loading, setLoading] = useState(true);
|
|
160
166
|
const [loadError, setLoadError] = useState<string | null>(null);
|
|
161
167
|
const [selectedSymbol, setSelectedSymbol] = useState<string | null>(null);
|
|
162
168
|
const [sortPreference, setSortPreference] = useState<MarketMoverSortPreference>(DEFAULT_SORT_PREFERENCE);
|
|
163
169
|
const [moversStale, setMoversStale] = useState(false);
|
|
164
|
-
const [lastSuccess, setLastSuccess] = useState<{
|
|
170
|
+
const [lastSuccess, setLastSuccess] = useState<{ key: string; at: number } | null>(null);
|
|
165
171
|
|
|
166
172
|
const fetchGenRef = useRef(0);
|
|
167
173
|
|
|
@@ -192,8 +198,8 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
192
198
|
// A pre-market or after-hours column only while a row has such a print.
|
|
193
199
|
const extendedSessionsKey = moverExtendedSessions(rankedRows).join(",");
|
|
194
200
|
const columns = useMemo(
|
|
195
|
-
() => buildMarketMoverColumns(width, extendedSessionsKey ? extendedSessionsKey.split(",") as ExtendedSession[] : []),
|
|
196
|
-
[extendedSessionsKey, width],
|
|
201
|
+
() => buildMarketMoverColumns(width, extendedSessionsKey ? extendedSessionsKey.split(",") as ExtendedSession[] : [], { float: minFloat != null }),
|
|
202
|
+
[extendedSessionsKey, minFloat, width],
|
|
197
203
|
);
|
|
198
204
|
const rows = useMemo(() => sortRows(rankedRows, sortPreference), [rankedRows, sortPreference]);
|
|
199
205
|
const selectedIdx = selectedSymbol
|
|
@@ -223,6 +229,7 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
223
229
|
try {
|
|
224
230
|
const result = await loadMarketMoverTab(tab, dataProvider, {
|
|
225
231
|
forceRefresh: options?.forceRefresh,
|
|
232
|
+
minFloat,
|
|
226
233
|
});
|
|
227
234
|
if (fetchGenRef.current !== gen) return;
|
|
228
235
|
|
|
@@ -230,12 +237,12 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
230
237
|
// A partial list shows only its current rows; a total failure keeps this list's prior rows.
|
|
231
238
|
if (!quoteError || result.quotes.length > 0) {
|
|
232
239
|
setQuotes(result.quotes);
|
|
233
|
-
|
|
240
|
+
setLoadedKey(`${tab}:${minFloat ?? ""}`);
|
|
234
241
|
if (!options?.background) setSelectedSymbol(null);
|
|
235
242
|
}
|
|
236
243
|
setMoversStale(result.stale);
|
|
237
244
|
setLoadError(quoteError);
|
|
238
|
-
if (!quoteError && !result.stale) setLastSuccess({ tab
|
|
245
|
+
if (!quoteError && !result.stale) setLastSuccess({ key: `${tab}:${minFloat ?? ""}`, at: Date.now() });
|
|
239
246
|
} catch {
|
|
240
247
|
if (fetchGenRef.current !== gen) return;
|
|
241
248
|
setMoversStale(true);
|
|
@@ -244,7 +251,7 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
244
251
|
finally {
|
|
245
252
|
if (fetchGenRef.current === gen && !options?.background) setLoading(false);
|
|
246
253
|
}
|
|
247
|
-
}, [dataProvider]);
|
|
254
|
+
}, [dataProvider, minFloat]);
|
|
248
255
|
|
|
249
256
|
useEffect(() => {
|
|
250
257
|
setSelectedSymbol(null);
|
|
@@ -254,7 +261,7 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
254
261
|
const backgroundRefresh = useCallback(() => {
|
|
255
262
|
void loadTab(activeTab, { background: true });
|
|
256
263
|
}, [activeTab, loadTab]);
|
|
257
|
-
useAutoRefresh(lastSuccess?.
|
|
264
|
+
useAutoRefresh(lastSuccess?.key === listKey ? lastSuccess.at : null, backgroundRefresh);
|
|
258
265
|
|
|
259
266
|
const openSymbol = useCallback((row: MarketMoverRow) => {
|
|
260
267
|
pinTicker(moverKey(row), { floating: true, paneType: TICKER_RESEARCH_PANE_ID, instrument: null });
|
|
@@ -281,16 +288,21 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
281
288
|
id: "feed",
|
|
282
289
|
parts: [{ text: feedStatus, tone: feedStatus === "live" ? "value" as const : "muted" as const }],
|
|
283
290
|
}] : []),
|
|
284
|
-
...(moversStale &&
|
|
291
|
+
...(moversStale && loadedKey === listKey ? [{
|
|
285
292
|
id: "stale",
|
|
286
293
|
parts: [{ text: "stale", tone: "muted" as const }],
|
|
287
294
|
}] : []),
|
|
295
|
+
// The FLOAT column shows while a minimum is set; a float the source does not date says so once.
|
|
296
|
+
...partialFloatFooter(columns.some((column) => column.id === "floatShares"), rows),
|
|
288
297
|
],
|
|
289
|
-
}), [
|
|
298
|
+
}), [columns, feedStatus, listKey, loadedKey, loadError, loading, moversStale, rows, summaryQuotes]);
|
|
290
299
|
|
|
291
300
|
return (
|
|
292
301
|
<DataTableView<MarketMoverRow, MarketMoverColumn>
|
|
293
302
|
focused={focused}
|
|
303
|
+
rootBefore={floatFiltered ? (
|
|
304
|
+
<QueryBar width={Math.max(1, width - 2)} filters={[minFloatFilter(minFloatValue, setMinFloatValue)]} />
|
|
305
|
+
) : undefined}
|
|
294
306
|
selection={{
|
|
295
307
|
kind: "id",
|
|
296
308
|
selectedId: selectedSymbol,
|
|
@@ -298,7 +310,7 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
298
310
|
onChange: (symbol) => setSelectedSymbol(symbol),
|
|
299
311
|
}}
|
|
300
312
|
onRootKeyDown={handleTableKeyDown}
|
|
301
|
-
resetScrollKey={
|
|
313
|
+
resetScrollKey={listKey}
|
|
302
314
|
sortable
|
|
303
315
|
columns={columns}
|
|
304
316
|
items={rows}
|
|
@@ -309,7 +321,7 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
309
321
|
onActivate={openSymbol}
|
|
310
322
|
renderCell={renderCell}
|
|
311
323
|
selectedTextOverridesCellColor
|
|
312
|
-
emptyStateTitle={loading ? "Loading movers..." : loadError ?? "No movers returned."}
|
|
324
|
+
emptyStateTitle={loading ? "Loading movers..." : loadError ?? (minFloat != null ? minFloatEmptyTitle(minFloatValue) : "No movers returned.")}
|
|
313
325
|
emptyContent={loadError ? (
|
|
314
326
|
<Box paddingX={1} paddingY={1}>
|
|
315
327
|
<EmptyState title={loadError} message="Try again in a moment." />
|