@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
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import {
|
|
2
|
+
apiClient,
|
|
3
|
+
type ScannerFlowSession,
|
|
4
|
+
type ScannerFlowSessionStrike,
|
|
5
|
+
} from "../../../api-client";
|
|
6
|
+
import { signalWithTimeout } from "../../../utils/async-deadline";
|
|
7
|
+
import { formatFlowPremium } from "./flow-model";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The session's recorded prints summed by the server under the pane's
|
|
11
|
+
* filters: the net-premium tide through the day and, for one name, its
|
|
12
|
+
* premium by expiry and strike. A print at the ask counts as bought and one at
|
|
13
|
+
* the bid as sold; mid or unknown prints count as neither (docs/usage.md, Options flow).
|
|
14
|
+
*/
|
|
15
|
+
export type FlowSessionLoader = (search: string, signal?: AbortSignal) => Promise<ScannerFlowSession>;
|
|
16
|
+
|
|
17
|
+
export const loadFlowSession: FlowSessionLoader = (search, signal) =>
|
|
18
|
+
apiClient.getScannerFlowSession(search, signalWithTimeout(signal, 20_000));
|
|
19
|
+
|
|
20
|
+
/** A summary is refetched this often while the tape is live; it is final once the session closes. */
|
|
21
|
+
export const FLOW_SESSION_LIVE_REFRESH_MS = 60_000;
|
|
22
|
+
|
|
23
|
+
/** One bucket of the tide: what it added, and the running totals through it. */
|
|
24
|
+
export interface FlowTideRow {
|
|
25
|
+
at: number;
|
|
26
|
+
/** Call premium bought at the ask less call premium sold at the bid, through this bucket. */
|
|
27
|
+
netCalls: number;
|
|
28
|
+
netPuts: number;
|
|
29
|
+
/** Net calls less net puts through this bucket: the tide. */
|
|
30
|
+
net: number;
|
|
31
|
+
/** What this bucket added to the tide. */
|
|
32
|
+
change: number;
|
|
33
|
+
callAsk: number;
|
|
34
|
+
callBid: number;
|
|
35
|
+
putAsk: number;
|
|
36
|
+
putBid: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The tide bucket by bucket, oldest first, each row with the running totals through it. */
|
|
40
|
+
export function flowTideRows(session: ScannerFlowSession | null | undefined): FlowTideRow[] {
|
|
41
|
+
let netCalls = 0;
|
|
42
|
+
let netPuts = 0;
|
|
43
|
+
return [...(session?.buckets ?? [])].sort((left, right) => left.at - right.at).map((bucket) => {
|
|
44
|
+
const calls = bucket.callAsk - bucket.callBid;
|
|
45
|
+
const puts = bucket.putAsk - bucket.putBid;
|
|
46
|
+
netCalls += calls;
|
|
47
|
+
netPuts += puts;
|
|
48
|
+
return {
|
|
49
|
+
at: bucket.at,
|
|
50
|
+
netCalls,
|
|
51
|
+
netPuts,
|
|
52
|
+
net: netCalls - netPuts,
|
|
53
|
+
change: calls - puts,
|
|
54
|
+
callAsk: bucket.callAsk,
|
|
55
|
+
callBid: bucket.callBid,
|
|
56
|
+
putAsk: bucket.putAsk,
|
|
57
|
+
putBid: bucket.putBid,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The buckets that moved the tide: one whose prints were all mid adds nothing to read. */
|
|
63
|
+
export function flowTideMoves(rows: readonly FlowTideRow[]): FlowTideRow[] {
|
|
64
|
+
return rows.filter((row) => row.callAsk !== 0 || row.callBid !== 0 || row.putAsk !== 0 || row.putBid !== 0);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The running totals at every bucket from the first to the last, so a line
|
|
69
|
+
* holds flat through a quiet stretch instead of sloping across it.
|
|
70
|
+
*/
|
|
71
|
+
export function flowTidePoints(rows: readonly FlowTideRow[], bucketMs: number): Array<Pick<FlowTideRow, "at" | "netCalls" | "netPuts" | "net">> {
|
|
72
|
+
const points: Array<Pick<FlowTideRow, "at" | "netCalls" | "netPuts" | "net">> = [];
|
|
73
|
+
let previous: FlowTideRow | null = null;
|
|
74
|
+
for (const row of rows) {
|
|
75
|
+
if (previous && bucketMs > 0) {
|
|
76
|
+
for (let at = previous.at + bucketMs; at < row.at; at += bucketMs) {
|
|
77
|
+
points.push({ at, netCalls: previous.netCalls, netPuts: previous.netPuts, net: previous.net });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
points.push({ at: row.at, netCalls: row.netCalls, netPuts: row.netPuts, net: row.net });
|
|
81
|
+
previous = row;
|
|
82
|
+
}
|
|
83
|
+
return points;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface FlowStrikeRow extends ScannerFlowSessionStrike {
|
|
87
|
+
id: string;
|
|
88
|
+
/** Net call premium less net put premium at this strike, by the same side rule as the tide. */
|
|
89
|
+
net: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function flowStrikeRows(session: ScannerFlowSession | null | undefined): FlowStrikeRow[] {
|
|
93
|
+
return (session?.strikes ?? []).map((strike) => ({
|
|
94
|
+
...strike,
|
|
95
|
+
id: `${strike.expiry}:${strike.strike}`,
|
|
96
|
+
net: strike.callAsk - strike.callBid - (strike.putAsk - strike.putBid),
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type FlowStrikeSort = "expiry" | "strike" | "callPremium" | "callAsk" | "callBid" | "putPremium" | "putAsk" | "putBid" | "net";
|
|
101
|
+
|
|
102
|
+
/** The breakdown in a column's order; ties fall back to expiry, then strike. */
|
|
103
|
+
export function sortFlowStrikes(
|
|
104
|
+
rows: readonly FlowStrikeRow[],
|
|
105
|
+
column: FlowStrikeSort | null,
|
|
106
|
+
direction: "asc" | "desc",
|
|
107
|
+
): FlowStrikeRow[] {
|
|
108
|
+
if (!column) return [...rows];
|
|
109
|
+
const sign = direction === "asc" ? 1 : -1;
|
|
110
|
+
const byContract = (left: FlowStrikeRow, right: FlowStrikeRow) =>
|
|
111
|
+
left.expiry.localeCompare(right.expiry) || left.strike - right.strike;
|
|
112
|
+
return [...rows].sort((left, right) => {
|
|
113
|
+
const primary = column === "expiry" ? left.expiry.localeCompare(right.expiry)
|
|
114
|
+
: column === "strike" ? left.strike - right.strike
|
|
115
|
+
: left[column] - right[column];
|
|
116
|
+
return primary * sign || byContract(left, right);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** A premium that can go either way, with its sign: `+$1.2M`, `-$350K`. */
|
|
121
|
+
export function formatSignedFlowPremium(value: number): string {
|
|
122
|
+
if (!Number.isFinite(value) || Math.abs(value) < 0.5) return "$0";
|
|
123
|
+
return `${value > 0 ? "+" : "-"}${formatFlowPremium(Math.abs(value))}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** An expiry as the table reads it: `10/30`, with the year when it is not this one. */
|
|
127
|
+
export function formatStrikeExpiry(expiry: string, now = Date.now()): string {
|
|
128
|
+
const [year, month, day] = expiry.split("-");
|
|
129
|
+
if (!year || !month || !day) return expiry;
|
|
130
|
+
return year === String(new Date(now).getFullYear()) ? `${month}/${day}` : `${month}/${day}/${year.slice(2)}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** A bucket's start as the local wall clock, `10:35`. */
|
|
134
|
+
export function formatTideTime(at: number): string {
|
|
135
|
+
const date = new Date(at);
|
|
136
|
+
return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** The session's date as the chart's legend names it: `Mon 26 Oct`. */
|
|
140
|
+
export function formatFlowSessionDate(session: string | null | undefined): string | null {
|
|
141
|
+
if (!session) return null;
|
|
142
|
+
const date = new Date(`${session}T12:00:00Z`);
|
|
143
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
144
|
+
return date.toLocaleDateString("en-US", { weekday: "short", day: "numeric", month: "short", timeZone: "UTC" })
|
|
145
|
+
.replace(/^(\w+), (\w+) (\d+)$/, "$1 $3 $2");
|
|
146
|
+
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { ApiRequestError } from "../../../api-client/errors";
|
|
2
2
|
import type {
|
|
3
|
+
ScannerFlowEvent,
|
|
4
|
+
ScannerFlowHistoryPage,
|
|
3
5
|
ScannerFlowPayload,
|
|
6
|
+
ScannerFlowSession,
|
|
4
7
|
ScannerHiloPayload,
|
|
5
8
|
ScannerKind,
|
|
6
9
|
ScannerPayload,
|
|
7
10
|
} from "../../../api-client";
|
|
8
11
|
import type {
|
|
12
|
+
HeadlessBundleSection,
|
|
9
13
|
HeadlessPaneApiClient,
|
|
14
|
+
HeadlessPaneColumn,
|
|
10
15
|
HeadlessPaneDefinition,
|
|
11
16
|
HeadlessPaneFreshness,
|
|
12
17
|
HeadlessPaneLoadArgs,
|
|
@@ -17,9 +22,20 @@ import {
|
|
|
17
22
|
DEFAULT_FLOW_FILTERS,
|
|
18
23
|
FLOW_FILTER_OPTIONS,
|
|
19
24
|
filterFlowEvents,
|
|
25
|
+
flowHistoryQuery,
|
|
26
|
+
flowHistorySearch,
|
|
27
|
+
flowScopeSymbol,
|
|
28
|
+
flowSessionSearch,
|
|
29
|
+
formatFlowPremium,
|
|
30
|
+
formatFlowVolOi,
|
|
31
|
+
mergeFlowRows,
|
|
20
32
|
type FlowFilters,
|
|
33
|
+
type FlowScope,
|
|
21
34
|
} from "./flow-model";
|
|
22
|
-
import {
|
|
35
|
+
import { flowStrikeRows, flowTideMoves, flowTideRows, formatSignedFlowPremium } from "./flow-session";
|
|
36
|
+
import { signalWithTimeout } from "../../../utils/async-deadline";
|
|
37
|
+
import { errorMessage } from "../../../utils/errors";
|
|
38
|
+
import { explainRefusal, refusedLock } from "../shared/cloud-denied";
|
|
23
39
|
import { captureScannerEvent, SCANNER_SNAPSHOT_TIMEOUT_MS } from "../shared/scanner-capture";
|
|
24
40
|
import { flowLockNote } from "./lock-note";
|
|
25
41
|
import { buildHiloBarRows, filterHiloRows, formatHiloPrice, type HiloMinPrice, type HiloSort } from "./hilo-model";
|
|
@@ -175,15 +191,121 @@ function flowOption(key: FlowHeadlessFilterKey): HeadlessPaneOptionDef {
|
|
|
175
191
|
};
|
|
176
192
|
}
|
|
177
193
|
|
|
178
|
-
|
|
179
|
-
|
|
194
|
+
/** The pane's tabs, each a section of the report; left out, the report has every one. */
|
|
195
|
+
type FlowReportTab = "prints" | "tide" | "strikes";
|
|
196
|
+
|
|
197
|
+
/** Recorded prints of one name a report lists under the live ones. */
|
|
198
|
+
const FLOW_REPORT_RECORDED = 100;
|
|
199
|
+
|
|
200
|
+
/** The side rule, said once in every report that sums prints by side. */
|
|
201
|
+
export const FLOW_SIDE_RULE = "Net premium counts a print at the ask as bought and one at the bid as sold; mid or unknown prints count as neither.";
|
|
202
|
+
|
|
203
|
+
const utcTime = (value: unknown) => {
|
|
204
|
+
const time = Number(value);
|
|
205
|
+
return Number.isFinite(time) ? `${new Date(time).toISOString().slice(0, 19).replace("T", " ")} UTC` : "-";
|
|
206
|
+
};
|
|
207
|
+
const signedPremium = (value: unknown) => typeof value === "number" ? formatSignedFlowPremium(value) : "";
|
|
208
|
+
/** A side's premium in the text view: `$2.1M`, blank where none traded. */
|
|
209
|
+
const premium = (value: unknown) => typeof value === "number" && value > 0 ? formatFlowPremium(value) : "";
|
|
210
|
+
|
|
211
|
+
const PRINT_COLUMNS: HeadlessPaneColumn[] = [
|
|
212
|
+
{ key: "at", header: "Time", format: utcTime },
|
|
213
|
+
{ key: "underlying", header: "Ticker" },
|
|
214
|
+
{ key: "right", header: "Right" },
|
|
215
|
+
{ key: "kind", header: "Kind" },
|
|
216
|
+
{ key: "strike", header: "Strike", align: "right" },
|
|
217
|
+
{ key: "expiry", header: "Expiry" },
|
|
218
|
+
{ key: "side", header: "Side" },
|
|
219
|
+
{ key: "size", header: "Size", align: "right" },
|
|
220
|
+
{ key: "premium", header: "Premium", align: "right", textFormat: premium },
|
|
221
|
+
{ key: "volOi", header: "Vol/OI", align: "right", textFormat: (value) => formatFlowVolOi(typeof value === "number" ? value : null) },
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
const TIDE_COLUMNS: HeadlessPaneColumn[] = [
|
|
225
|
+
{ key: "at", header: "Time", format: utcTime },
|
|
226
|
+
{ key: "callAsk", header: "Call ask", align: "right", description: "Call premium bought at the ask in the bucket." },
|
|
227
|
+
{ key: "callBid", header: "Call bid", align: "right", description: "Call premium sold at the bid in the bucket." },
|
|
228
|
+
{ key: "putAsk", header: "Put ask", align: "right", description: "Put premium bought at the ask in the bucket." },
|
|
229
|
+
{ key: "putBid", header: "Put bid", align: "right", description: "Put premium sold at the bid in the bucket." },
|
|
230
|
+
{ key: "netCalls", header: "Net calls", align: "right", textFormat: signedPremium, description: "Call ask less call bid premium through the bucket." },
|
|
231
|
+
{ key: "netPuts", header: "Net puts", align: "right", textFormat: signedPremium, description: "Put ask less put bid premium through the bucket." },
|
|
232
|
+
{ key: "net", header: "Net premium", align: "right", textFormat: signedPremium, description: "Net calls less net puts through the bucket: the tide." },
|
|
233
|
+
{ key: "change", header: "Change", align: "right", textFormat: signedPremium, description: "What the bucket added to the net premium." },
|
|
234
|
+
];
|
|
235
|
+
const TIDE_TEXT_COLUMNS = TIDE_COLUMNS.filter((column) => !["callAsk", "callBid", "putAsk", "putBid"].includes(column.key));
|
|
236
|
+
|
|
237
|
+
const STRIKE_COLUMNS: HeadlessPaneColumn[] = [
|
|
238
|
+
{ key: "expiry", header: "Expiry" },
|
|
239
|
+
{ key: "strike", header: "Strike", align: "right" },
|
|
240
|
+
{ key: "callPremium", header: "Calls", align: "right", textFormat: premium, description: "All call premium, mid and unknown prints included." },
|
|
241
|
+
{ key: "callAsk", header: "Call ask", align: "right", textFormat: premium },
|
|
242
|
+
{ key: "callBid", header: "Call bid", align: "right", textFormat: premium },
|
|
243
|
+
{ key: "putPremium", header: "Puts", align: "right", textFormat: premium, description: "All put premium, mid and unknown prints included." },
|
|
244
|
+
{ key: "putAsk", header: "Put ask", align: "right", textFormat: premium },
|
|
245
|
+
{ key: "putBid", header: "Put bid", align: "right", textFormat: premium },
|
|
246
|
+
{ key: "net", header: "Net", align: "right", textFormat: signedPremium, description: "Net call premium less net put premium at the strike." },
|
|
247
|
+
{ key: "prints", header: "Prints", align: "right" },
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
interface FlowReportData {
|
|
251
|
+
payload: ScannerFlowPayload;
|
|
252
|
+
/** One name's recorded prints below the live tape; null for the market. */
|
|
253
|
+
recorded: ScannerFlowHistoryPage | null;
|
|
254
|
+
/** The session's prints summed; null when the report leaves the tide and strikes out. */
|
|
255
|
+
session: ScannerFlowSession | null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Newest first, the buckets that moved the tide with the running totals through each, as the pane's table lists them. */
|
|
259
|
+
function tideReportRows(session: ScannerFlowSession | null): Array<Record<string, unknown>> {
|
|
260
|
+
return flowTideMoves(flowTideRows(session)).reverse().map((row) => ({ ...row }));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function projectFlowReport(
|
|
264
|
+
data: FlowReportData,
|
|
180
265
|
filters: FlowFilters,
|
|
266
|
+
scope: FlowScope,
|
|
267
|
+
tab: FlowReportTab | null,
|
|
181
268
|
now = Date.now(),
|
|
182
|
-
):
|
|
183
|
-
const
|
|
269
|
+
): { sections: HeadlessBundleSection[]; asOf: string; metadata: Record<string, unknown>; notes?: string[]; errors?: string[]; freshness: HeadlessPaneFreshness } {
|
|
270
|
+
const { payload, recorded, session } = data;
|
|
271
|
+
const symbol = scope.symbol ?? null;
|
|
272
|
+
const live = filterFlowEvents(payload.events, filters, scope, now);
|
|
273
|
+
const prints: ScannerFlowEvent[] = recorded ? mergeFlowRows(live, recorded.events) : live;
|
|
274
|
+
const shows = (section: FlowReportTab) => !tab || tab === section;
|
|
275
|
+
const sessionDate = session?.session ?? null;
|
|
276
|
+
const sections: HeadlessBundleSection[] = [];
|
|
277
|
+
if (shows("prints")) {
|
|
278
|
+
sections.push({
|
|
279
|
+
title: symbol ? `${symbol} prints` : "Prints",
|
|
280
|
+
columns: PRINT_COLUMNS,
|
|
281
|
+
rows: prints.map((event) => ({ ...event })),
|
|
282
|
+
empty: symbol ? `No ${symbol} prints under these filters.` : "No prints on the tape under these filters.",
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (shows("tide")) {
|
|
286
|
+
sections.push({
|
|
287
|
+
title: sessionDate ? `Net premium through the ${sessionDate} session` : "Net premium through the session",
|
|
288
|
+
columns: TIDE_COLUMNS,
|
|
289
|
+
textColumns: TIDE_TEXT_COLUMNS,
|
|
290
|
+
rows: tideReportRows(session),
|
|
291
|
+
unit: "USD",
|
|
292
|
+
empty: "No prints under these filters this session.",
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
if (symbol && shows("strikes")) {
|
|
296
|
+
sections.push({
|
|
297
|
+
title: sessionDate ? `${symbol} by expiry and strike, ${sessionDate}` : `${symbol} by expiry and strike`,
|
|
298
|
+
columns: STRIKE_COLUMNS,
|
|
299
|
+
rows: flowStrikeRows(session).map(({ id: _id, ...row }) => row),
|
|
300
|
+
unit: "USD",
|
|
301
|
+
empty: `No ${symbol} prints under these filters this session.`,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
const sums = shows("tide") || (!!symbol && shows("strikes"));
|
|
184
305
|
return {
|
|
306
|
+
sections,
|
|
185
307
|
asOf: new Date(payload.asOf).toISOString(),
|
|
186
|
-
|
|
308
|
+
...(sums ? { notes: [FLOW_SIDE_RULE] } : {}),
|
|
187
309
|
...(payload.status === "degraded" ? { errors: ["Scanner feed degraded"] } : {}),
|
|
188
310
|
freshness: scannerFreshness(payload),
|
|
189
311
|
metadata: {
|
|
@@ -192,6 +314,8 @@ function projectFlowSnapshot(
|
|
|
192
314
|
delayMinutes: payload.delayMinutes,
|
|
193
315
|
received: payload.events?.length ?? 0,
|
|
194
316
|
filters,
|
|
317
|
+
...(symbol ? { symbol } : {}),
|
|
318
|
+
...(session ? { session: session.session, bucketMinutes: session.bucketMs / 60_000 } : {}),
|
|
195
319
|
},
|
|
196
320
|
};
|
|
197
321
|
}
|
|
@@ -202,46 +326,91 @@ interface FlowHeadlessDependencies {
|
|
|
202
326
|
client: Pick<HeadlessPaneApiClient, "subscribeScanner">,
|
|
203
327
|
signal: AbortSignal,
|
|
204
328
|
): Promise<ScannerFlowPayload>;
|
|
329
|
+
recorded(search: string, client: Pick<HeadlessPaneApiClient, "getScannerFlowHistory">, signal: AbortSignal): Promise<ScannerFlowHistoryPage>;
|
|
330
|
+
session(search: string, client: Pick<HeadlessPaneApiClient, "getScannerFlowSession">, signal: AbortSignal): Promise<ScannerFlowSession>;
|
|
205
331
|
}
|
|
206
332
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
333
|
+
const flowDependencies: FlowHeadlessDependencies = {
|
|
334
|
+
load: (_args, client, signal) => loadScannerSnapshot<ScannerFlowPayload>(client, "flow", signal),
|
|
335
|
+
recorded: (search, client, signal) => client.getScannerFlowHistory(search, signalWithTimeout(signal, 20_000)),
|
|
336
|
+
session: (search, client, signal) => client.getScannerFlowSession(search, signalWithTimeout(signal, 20_000)),
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
export function createFlowHeadless(
|
|
340
|
+
dependencies: FlowHeadlessDependencies = flowDependencies,
|
|
341
|
+
): HeadlessPaneDefinition<"bundle"> {
|
|
212
342
|
const keys: FlowHeadlessFilterKey[] = ["minPremium", "side", "kind", "volOi", "expiry"];
|
|
213
343
|
return {
|
|
214
|
-
shape: "
|
|
215
|
-
argument: {
|
|
216
|
-
|
|
217
|
-
|
|
344
|
+
shape: "bundle",
|
|
345
|
+
argument: {
|
|
346
|
+
kind: "ticker",
|
|
347
|
+
optional: true,
|
|
348
|
+
placeholder: "ticker",
|
|
349
|
+
description: "Optional ticker: its prints and its premium by expiry and strike. Omit for the whole market.",
|
|
350
|
+
},
|
|
351
|
+
options: [
|
|
352
|
+
...keys.map(flowOption),
|
|
218
353
|
{
|
|
219
|
-
key: "
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
},
|
|
354
|
+
key: "tab",
|
|
355
|
+
settingKey: "tab",
|
|
356
|
+
description: "One section: the prints, the net-premium tide through the session, or a ticker's strikes. Every section when left out.",
|
|
357
|
+
type: "enum",
|
|
358
|
+
values: [{ value: "prints" }, { value: "tide" }, { value: "strikes" }],
|
|
225
359
|
},
|
|
226
|
-
{ key: "underlying", header: "Ticker" },
|
|
227
|
-
{ key: "right", header: "Right" },
|
|
228
|
-
{ key: "kind", header: "Kind" },
|
|
229
|
-
{ key: "strike", header: "Strike", align: "right" },
|
|
230
|
-
{ key: "expiry", header: "Expiry" },
|
|
231
|
-
{ key: "side", header: "Side" },
|
|
232
|
-
{ key: "size", header: "Size", align: "right" },
|
|
233
|
-
{ key: "premium", header: "Premium", align: "right" },
|
|
234
|
-
{ key: "volOi", header: "Vol/OI", align: "right" },
|
|
235
360
|
],
|
|
236
|
-
describe:
|
|
361
|
+
describe: (args) => {
|
|
362
|
+
const symbol = flowScopeSymbol(Array.isArray(args.argument) ? args.argument[0] : args.argument);
|
|
363
|
+
return symbol ? `Options Flow: ${symbol}` : "Options Flow";
|
|
364
|
+
},
|
|
237
365
|
async load(args, ctx) {
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
366
|
+
const symbol = flowScopeSymbol(Array.isArray(args.argument) ? args.argument[0] : args.argument);
|
|
367
|
+
const tab = typeof args.options.tab === "string" && args.options.tab ? args.options.tab as FlowReportTab : null;
|
|
368
|
+
if (tab === "strikes" && !symbol) throw new Error("--tab strikes needs a ticker: gloom fn FLOW <ticker> --tab strikes.");
|
|
369
|
+
const filters: FlowFilters = {
|
|
241
370
|
...DEFAULT_FLOW_FILTERS,
|
|
242
371
|
...(args.options as Partial<FlowFilters>),
|
|
243
372
|
universe: "active",
|
|
244
|
-
}
|
|
373
|
+
};
|
|
374
|
+
const scope: FlowScope = { symbol, watchlist: new Set() };
|
|
375
|
+
const refuse = async (error: unknown): Promise<never> => {
|
|
376
|
+
throw await explainRefusal(error, flowLockNote, { symbol, context: ctx });
|
|
377
|
+
};
|
|
378
|
+
// The live feed answers the entitlement first, as the pane's does.
|
|
379
|
+
const payload = await dependencies.load(args, ctx.apiClient, ctx.signal).catch(refuse);
|
|
380
|
+
// The recorded prints and the session's sums add to the tape: one that
|
|
381
|
+
// fails (a timeout, a server without the route) is said under errors and
|
|
382
|
+
// the rest of the report stands. A wall still refuses the whole read.
|
|
383
|
+
const failures: string[] = [];
|
|
384
|
+
const extra = async <T>(load: Promise<T> | null, what: string): Promise<T | null> => {
|
|
385
|
+
if (!load) return null;
|
|
386
|
+
try {
|
|
387
|
+
return await load;
|
|
388
|
+
} catch (error) {
|
|
389
|
+
if (ctx.signal.aborted || refusedLock(error)) return refuse(error);
|
|
390
|
+
failures.push(`${what} unavailable: ${errorMessage(error)}`);
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
const wantsPrints = !tab || tab === "prints";
|
|
395
|
+
const wantsSession = !tab || tab === "tide" || tab === "strikes";
|
|
396
|
+
const [recorded, session] = await Promise.all([
|
|
397
|
+
extra(symbol && wantsPrints
|
|
398
|
+
? dependencies.recorded(flowHistorySearch(flowHistoryQuery(filters, scope, undefined, FLOW_REPORT_RECORDED)), ctx.apiClient, ctx.signal)
|
|
399
|
+
: null, "Recorded prints"),
|
|
400
|
+
extra(wantsSession ? dependencies.session(flowSessionSearch(filters, scope), ctx.apiClient, ctx.signal) : null, "Session totals"),
|
|
401
|
+
]);
|
|
402
|
+
// Asked for the session's sums alone, a failure to read them is the report's.
|
|
403
|
+
if (!wantsPrints && failures.length > 0) throw new Error(failures[0]);
|
|
404
|
+
const report = projectFlowReport({ payload, recorded, session }, filters, scope, wantsSession && !session ? "prints" : tab);
|
|
405
|
+
const errors = [...report.errors ?? [], ...failures];
|
|
406
|
+
return {
|
|
407
|
+
sections: report.sections,
|
|
408
|
+
...(report.notes ? { notes: report.notes } : {}),
|
|
409
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
410
|
+
...(symbol ? { symbols: [symbol] } : {}),
|
|
411
|
+
freshness: report.freshness,
|
|
412
|
+
metadata: { asOf: report.asOf, ...report.metadata },
|
|
413
|
+
};
|
|
245
414
|
},
|
|
246
415
|
};
|
|
247
416
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PaneSettingsDef } from "../../../types/plugin";
|
|
2
2
|
import type { PluginModule } from "../plugin-module";
|
|
3
|
-
import { DEFAULT_FLOW_FILTERS, FLOW_FILTER_OPTIONS } from "./flow-model";
|
|
3
|
+
import { DEFAULT_FLOW_FILTERS, FLOW_FILTER_OPTIONS, flowScopeSymbol } from "./flow-model";
|
|
4
4
|
import FlowPane from "./flow-pane";
|
|
5
5
|
import HiloPane from "./hilo-pane";
|
|
6
6
|
import LiveScanPane from "./live-pane";
|
|
@@ -123,11 +123,22 @@ export const scannerModule: PluginModule = {
|
|
|
123
123
|
id: "scanner-flow-pane",
|
|
124
124
|
paneId: FLOW_PANE_ID,
|
|
125
125
|
label: "Options Flow",
|
|
126
|
-
description: "Unusual options activity: sweeps, blocks, and
|
|
127
|
-
keywords: ["flow", "options", "sweep", "block", "unusual", "premium", "scanner"],
|
|
128
|
-
shortcut: { prefix: "FLOW" },
|
|
126
|
+
description: "Unusual options activity: sweeps, blocks, large premium prints, and the net premium tide.",
|
|
127
|
+
keywords: ["flow", "options", "sweep", "block", "unusual", "premium", "scanner", "tide", "net premium", "market tide"],
|
|
128
|
+
shortcut: { prefix: "FLOW", argKind: "ticker", argPlaceholder: "ticker", argOptional: true, openWithoutArg: true },
|
|
129
129
|
headless: flowHeadless,
|
|
130
|
-
|
|
130
|
+
// One window per name, and one for the whole market.
|
|
131
|
+
createInstance: (_context, options) => {
|
|
132
|
+
const symbol = flowScopeSymbol(options?.symbol ?? options?.arg);
|
|
133
|
+
return symbol
|
|
134
|
+
? {
|
|
135
|
+
instanceId: `${FLOW_PANE_ID}:${symbol}`,
|
|
136
|
+
title: `FLOW ${symbol}`,
|
|
137
|
+
binding: { kind: "fixed" as const, symbol },
|
|
138
|
+
settings: { ...DEFAULT_FLOW_FILTERS, symbol },
|
|
139
|
+
}
|
|
140
|
+
: { settings: { ...DEFAULT_FLOW_FILTERS } };
|
|
141
|
+
},
|
|
131
142
|
},
|
|
132
143
|
{
|
|
133
144
|
id: LIVE_SCAN_TEMPLATE_ID,
|
|
@@ -4,7 +4,18 @@ import type {
|
|
|
4
4
|
HeadlessPaneFreshness,
|
|
5
5
|
HeadlessSnapshotResult,
|
|
6
6
|
} from "../../../types/plugin";
|
|
7
|
-
import {
|
|
7
|
+
import { formatPercentRaw } from "../../../utils/format";
|
|
8
|
+
import {
|
|
9
|
+
conditionColumnLabel,
|
|
10
|
+
conditionText,
|
|
11
|
+
DEFAULT_LIVE_SCAN,
|
|
12
|
+
formatMultiple,
|
|
13
|
+
LIVE_SCAN_SECTORS,
|
|
14
|
+
parseUniverseValue,
|
|
15
|
+
readLiveScan,
|
|
16
|
+
warmingWindowsText,
|
|
17
|
+
type LiveScan,
|
|
18
|
+
} from "./live-model";
|
|
8
19
|
import { LIVE_SCAN_INDEXES } from "../../../api-client/live-scan";
|
|
9
20
|
|
|
10
21
|
/** `all`, an index (`sp500`) or a sector (`Energy`), as `--universe` takes it. */
|
|
@@ -39,9 +50,21 @@ function freshness(payload: LiveScanPayload): HeadlessPaneFreshness {
|
|
|
39
50
|
return payload.access === "realtime" ? { status: "live" } : { status: "delayed", delayMinutes: payload.delayMinutes };
|
|
40
51
|
}
|
|
41
52
|
|
|
53
|
+
/** Why a scan lists nothing, as the pane says it: still starting, warming up, closed, or no match. */
|
|
54
|
+
function emptyText(payload: LiveScanPayload): string {
|
|
55
|
+
if (payload.status === "starting") return "The scanner is starting. Matches appear within a minute.";
|
|
56
|
+
if (payload.status === "closed") return "Nothing matched at the close. The scan runs again at the open.";
|
|
57
|
+
const warming = warmingWindowsText(payload);
|
|
58
|
+
if (warming) return `The scanner is warming up: conditions over ${warming} match nothing until that much of the session has been watched.`;
|
|
59
|
+
return "Nothing matches right now.";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const number = (value: unknown) => (typeof value === "number" ? value : undefined);
|
|
63
|
+
|
|
42
64
|
function projectLiveScan(payload: LiveScanPayload, scan: LiveScan): HeadlessSnapshotResult {
|
|
43
65
|
return {
|
|
44
66
|
asOf: new Date(payload.asOf ?? payload.takenAt ?? Date.now()).toISOString(),
|
|
67
|
+
empty: emptyText(payload),
|
|
45
68
|
items: payload.matches.map((match) => ({
|
|
46
69
|
symbol: match.symbol,
|
|
47
70
|
exchange: match.exchange ?? null,
|
|
@@ -64,6 +87,7 @@ function projectLiveScan(payload: LiveScanPayload, scan: LiveScan): HeadlessSnap
|
|
|
64
87
|
scanned: payload.scanned,
|
|
65
88
|
matched: payload.matched,
|
|
66
89
|
conditions: scan.conditions.map(conditionText),
|
|
90
|
+
...(payload.warmingWindows?.length ? { warmingWindows: payload.warmingWindows } : {}),
|
|
67
91
|
},
|
|
68
92
|
};
|
|
69
93
|
}
|
|
@@ -94,8 +118,8 @@ export const liveScanHeadless: HeadlessPaneDefinition<"snapshot"> = {
|
|
|
94
118
|
{ key: "symbol", header: "Symbol" },
|
|
95
119
|
{ key: "name", header: "Name" },
|
|
96
120
|
{ key: "price", header: "Price", align: "right" },
|
|
97
|
-
{ key: "changePercent", header: "Chg %", align: "right" },
|
|
98
|
-
{ key: "relativeVolume", header: "RVol", align: "right" },
|
|
121
|
+
{ key: "changePercent", header: "Chg %", align: "right", format: (value) => formatPercentRaw(number(value)) },
|
|
122
|
+
{ key: "relativeVolume", header: "RVol", align: "right", format: (value) => formatMultiple(number(value)) },
|
|
99
123
|
{ key: "volume", header: "Volume", align: "right" },
|
|
100
124
|
],
|
|
101
125
|
describe: "Live Scanner",
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type LiveScanDirection,
|
|
6
6
|
type LiveScanIndex,
|
|
7
7
|
type LiveScanMatch,
|
|
8
|
+
type LiveScanPayload,
|
|
8
9
|
type LiveScanWindow,
|
|
9
10
|
type LiveScanWireDefinition,
|
|
10
11
|
} from "../../../api-client/live-scan";
|
|
@@ -208,6 +209,16 @@ export function formatMultiple(value: number | null | undefined): string {
|
|
|
208
209
|
return value >= 10 ? `${Math.round(value)}x` : `${value.toFixed(1)}x`;
|
|
209
210
|
}
|
|
210
211
|
|
|
212
|
+
/**
|
|
213
|
+
* The windows a running scan's conditions cannot measure yet (the scanner
|
|
214
|
+
* started recently), as a list to read: `5 min, 15 min`. Empty when there are
|
|
215
|
+
* none, or when the scanner is not running a session.
|
|
216
|
+
*/
|
|
217
|
+
export function warmingWindowsText(payload: LiveScanPayload | null): string {
|
|
218
|
+
const windows = payload?.status === "live" || payload?.status === "degraded" ? payload.warmingWindows : [];
|
|
219
|
+
return (windows ?? []).map((window) => `${window} min`).join(", ");
|
|
220
|
+
}
|
|
221
|
+
|
|
211
222
|
/** A time of day the scanner saw something, to the second. */
|
|
212
223
|
export function formatScanTime(at: number | null | undefined): string {
|
|
213
224
|
if (at == null || !Number.isFinite(at)) return "--";
|
|
@@ -60,6 +60,7 @@ import {
|
|
|
60
60
|
universeLabel,
|
|
61
61
|
universeOptions,
|
|
62
62
|
universeValue,
|
|
63
|
+
warmingWindowsText,
|
|
63
64
|
type LiveScan,
|
|
64
65
|
type SavedLiveScan,
|
|
65
66
|
} from "./live-model";
|
|
@@ -194,9 +195,8 @@ function emptyState(payload: LiveScanPayload | null): { title: string; hint?: st
|
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
function warmingNotice(payload: LiveScanPayload | null): string | null {
|
|
197
|
-
const
|
|
198
|
-
if (!
|
|
199
|
-
const list = windows.map((window) => `${window} min`).join(", ");
|
|
198
|
+
const list = warmingWindowsText(payload);
|
|
199
|
+
if (!list) return null;
|
|
200
200
|
return `The scanner started recently: conditions over ${list} match nothing until that much of the session has been watched.`;
|
|
201
201
|
}
|
|
202
202
|
|
|
@@ -5,12 +5,13 @@ import { proLockNote } from "../shared/pro-lock-note";
|
|
|
5
5
|
* FLOW is Pro whole: the one scanner with no delayed tier to fall back to.
|
|
6
6
|
* The options chain needs no account and lists each contract's day volume and
|
|
7
7
|
* open interest, the raw counts behind a sweep or a block, without the prints.
|
|
8
|
-
* FLOW takes
|
|
8
|
+
* FLOW takes a ticker only to narrow the market to one name, so the note
|
|
9
|
+
* repeats it only when the reader gave one.
|
|
9
10
|
*/
|
|
10
11
|
export function flowLockNote(lock: PaneLock): Promise<string> {
|
|
11
12
|
return proLockNote(lock, {
|
|
12
13
|
token: "FLOW",
|
|
13
|
-
argument: "
|
|
14
|
+
argument: "optional",
|
|
14
15
|
does: "it streams real-time sweeps, blocks and large premium prints across US options exchanges",
|
|
15
16
|
shows: "it",
|
|
16
17
|
free: [{ text: (ticker, company) => `gloom options ${ticker} lists ${company}'s option chain, each contract with its volume and open interest.` }],
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { HeadlessPaneDefinition } from "../../../types/plugin";
|
|
2
2
|
import { resolveHeadlessInstrument } from "../shared/headless-market-data";
|
|
3
3
|
import { loadSeasonalityHistory } from "./client";
|
|
4
|
+
import { closedHistoryBarDay, formingHistoryBar } from "../ticker-detail/data-panes/history-bars";
|
|
4
5
|
import { MONTH_LABELS, projectSeasonality, projectWeekdays, TURN_OF_MONTH_LABELS } from "./model";
|
|
5
6
|
|
|
6
7
|
const percent = (digits: number) => (value: unknown) => typeof value === "number" ? `${(value * 100).toFixed(digits)}%` : "--";
|
|
@@ -9,7 +10,7 @@ const statColumns = (digits: number, countHeader: string) => [{ key: "mean", hea
|
|
|
9
10
|
|
|
10
11
|
export const seasonalityHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
11
12
|
shape: "bundle", argument: { kind: "ticker", description: "Ticker" },
|
|
12
|
-
// Dated by the month
|
|
13
|
+
// Dated by its last monthly close, or by how far the running month's bar runs (`month to date, through Fri 23 Oct 2026`).
|
|
13
14
|
freshness: { status: "not-a-feed", basis: "monthly closes", maxAgeMinutes: 45 * 24 * 60 },
|
|
14
15
|
describe: (args) => `SEAS ${args.symbols[0] ?? ""}`,
|
|
15
16
|
discovery: { screenshotReadiness: "live-dom", limitations: ["Local-price closes; dividends and FX are excluded.",
|
|
@@ -46,6 +47,13 @@ export const seasonalityHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
const model = projectSeasonality(history.history, { symbol: instrument.symbol, lookbackYears: Number(args.options.lookbackYears) || 10 });
|
|
50
|
+
// Monthly bars are dated by their month's first day, which is no close while the month runs.
|
|
51
|
+
const bar = model.asOf?.toISOString().slice(0, 10);
|
|
52
|
+
// The venue whose sessions say whether the month still runs: the key's, or for a bare symbol its quote's.
|
|
53
|
+
const quote = bar && !instrument.exchange ? await ctx.marketData.getQuote(instrument.symbol, "").catch(() => null) : null;
|
|
54
|
+
const venue = instrument.exchange || quote?.listingExchangeName || quote?.exchangeName || undefined;
|
|
55
|
+
const forming = formingHistoryBar("1mo", bar, venue, Date.now());
|
|
56
|
+
const closed = forming ? null : closedHistoryBarDay("1mo", bar, venue, Date.now());
|
|
49
57
|
const monthColumns = MONTH_LABELS.map((label, month) => ({ key: `m${month}`, header: label, format: percent(1) }));
|
|
50
58
|
return {
|
|
51
59
|
sections: [
|
|
@@ -57,6 +65,7 @@ export const seasonalityHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
57
65
|
complete: !history.stale && !history.error && model.years.length > 0,
|
|
58
66
|
unavailableSymbols: model.years.length ? [] : [instrument.symbol],
|
|
59
67
|
...common,
|
|
68
|
+
...(forming ? { freshness: { periodToDate: forming } } : closed ? { freshness: { asOf: closed } } : {}),
|
|
60
69
|
metadata: { unit: "decimal return, local-price close to close", asOf: model.asOf?.toISOString() ?? null, stale: history.stale,
|
|
61
70
|
fetchedAt: history.fetchedAt, averagePath: model.averagePath, methodology: "docs/research-data.md#seasonality" },
|
|
62
71
|
};
|