gloomberb 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
|
@@ -1,21 +1,32 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import type { ScannerFlowEvent } from "../../../api-client";
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
|
2
|
+
import type { ScannerFlowEvent, ScannerFlowSession } from "../../../api-client";
|
|
3
3
|
import {
|
|
4
|
+
ChartTableHeader,
|
|
4
5
|
DataTableView,
|
|
5
6
|
PaneStatusBody,
|
|
6
7
|
QueryBar,
|
|
8
|
+
scalarPoint,
|
|
9
|
+
staticSeries,
|
|
10
|
+
useChartTableSelection,
|
|
11
|
+
usePaneTabs,
|
|
7
12
|
useTableLoadMore,
|
|
13
|
+
type ChartTableChart,
|
|
8
14
|
type DataTableCell,
|
|
9
15
|
type DataTableColumn,
|
|
10
16
|
type DataTableKeyEvent,
|
|
11
17
|
type PaneFooterSegment,
|
|
12
18
|
} from "../../../components";
|
|
19
|
+
import { fitChartTableColumns, formatCompactAxis } from "../../../components/chart-table";
|
|
20
|
+
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
21
|
+
import type { CompositeAxisDomain } from "../../../components/chart/composite/types";
|
|
22
|
+
import { useAsyncResource, useAutoRefresh, usePluginAppActions, usePluginPaneState } from "../../../public/react";
|
|
13
23
|
import { useAppSelector, usePaneSettingValue, usePaneStateValue } from "../../../state/app/context";
|
|
14
24
|
import { colors } from "../../../theme/colors";
|
|
25
|
+
import type { ResolvedSeries } from "../../../time-series/types";
|
|
15
26
|
import { TICKER_RESEARCH_PANE_ID } from "../../../types/config";
|
|
16
27
|
import type { PaneProps } from "../../../types/plugin";
|
|
17
|
-
import { Box, TextAttributes, type ScrollBoxRenderable } from "../../../ui";
|
|
18
|
-
import { formatCompact, formatNumber } from "../../../utils/format";
|
|
28
|
+
import { Box, Text, TextAttributes, type ScrollBoxRenderable } from "../../../ui";
|
|
29
|
+
import { MISSING_VALUE, formatCompact, formatNumber } from "../../../utils/format";
|
|
19
30
|
import { isPlainKey } from "../../../utils/keyboard";
|
|
20
31
|
import { usePluginTickerActions } from "../../runtime";
|
|
21
32
|
import { ScannerDeniedState } from "./denied";
|
|
@@ -28,6 +39,8 @@ import {
|
|
|
28
39
|
flowEmptyState,
|
|
29
40
|
flowHistoryQuery,
|
|
30
41
|
flowRowsSpanDays,
|
|
42
|
+
flowScopeSymbol,
|
|
43
|
+
flowSessionSearch,
|
|
31
44
|
keepFlowPrints,
|
|
32
45
|
mergeFlowRows,
|
|
33
46
|
formatFlowExpiry,
|
|
@@ -40,23 +53,51 @@ import {
|
|
|
40
53
|
type FlowFilters,
|
|
41
54
|
type FlowKind,
|
|
42
55
|
type FlowMinPremium,
|
|
56
|
+
type FlowScope,
|
|
43
57
|
type FlowSide,
|
|
44
58
|
type FlowUniverse,
|
|
45
59
|
type FlowVolOi,
|
|
46
60
|
} from "./flow-model";
|
|
61
|
+
import {
|
|
62
|
+
FLOW_SESSION_LIVE_REFRESH_MS,
|
|
63
|
+
flowStrikeRows,
|
|
64
|
+
flowTideMoves,
|
|
65
|
+
flowTidePoints,
|
|
66
|
+
flowTideRows,
|
|
67
|
+
formatFlowSessionDate,
|
|
68
|
+
formatSignedFlowPremium,
|
|
69
|
+
formatStrikeExpiry,
|
|
70
|
+
formatTideTime,
|
|
71
|
+
loadFlowSession,
|
|
72
|
+
sortFlowStrikes,
|
|
73
|
+
type FlowStrikeRow,
|
|
74
|
+
type FlowStrikeSort,
|
|
75
|
+
type FlowTideRow,
|
|
76
|
+
} from "./flow-session";
|
|
77
|
+
|
|
78
|
+
type FlowTab = "prints" | "tide" | "strikes";
|
|
47
79
|
|
|
48
|
-
|
|
80
|
+
/** The tabs a pane offers: the strike breakdown is one name's. */
|
|
81
|
+
function flowTabs(symbol: string | null): Array<{ label: string; value: FlowTab }> {
|
|
82
|
+
return [
|
|
83
|
+
{ label: "Prints", value: "prints" },
|
|
84
|
+
{ label: "Tide", value: "tide" },
|
|
85
|
+
...(symbol ? [{ label: "Strikes", value: "strikes" as const }] : []),
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function buildColumns(width: number, dated: boolean, ticker: boolean): DataTableColumn[] {
|
|
49
90
|
// Prints from earlier days carry their date ("09/22 15:04"), so the time
|
|
50
|
-
// column widens for them.
|
|
51
|
-
const fixed = { time: dated ? 11 : 8, ticker: 7, type: 8, strike: 8, exp: 6, side: 5, size: 7, prem: 7, volOi: 6 };
|
|
91
|
+
// column widens for them. One name's pane names it in its title, not a column.
|
|
92
|
+
const fixed = { time: dated ? 11 : 8, ticker: ticker ? 7 : 0, type: 8, strike: 8, exp: 6, side: 5, size: 7, prem: 7, volOi: 6 };
|
|
52
93
|
const total = Object.values(fixed).reduce((sum, value) => sum + value, 0);
|
|
53
94
|
// Table chrome is one gap per column, the table's extra gutter where
|
|
54
95
|
// right-aligned EXP meets left-aligned SIDE, two cells of padding, and the scrollbar.
|
|
55
|
-
const slack = Math.max(0, width - total - 9 - 1 - 2 - 1);
|
|
96
|
+
const slack = Math.max(0, width - total - (ticker ? 9 : 8) - 1 - 2 - 1);
|
|
56
97
|
return [
|
|
57
98
|
{ id: "time", label: "TIME", width: fixed.time, align: "left" },
|
|
58
|
-
{ id: "ticker", label: "TICKER", width: fixed.ticker + Math.min(4, slack), align: "left" },
|
|
59
|
-
{ id: "type", label: "TYPE", width: fixed.type, align: "left" },
|
|
99
|
+
...(ticker ? [{ id: "ticker", label: "TICKER", width: fixed.ticker + Math.min(4, slack), align: "left" as const }] : []),
|
|
100
|
+
{ id: "type", label: "TYPE", width: fixed.type + (ticker ? 0 : Math.min(4, slack)), align: "left" },
|
|
60
101
|
{ id: "strike", label: "STRIKE", width: fixed.strike, align: "right" },
|
|
61
102
|
{ id: "expiry", label: "EXP", width: fixed.exp, align: "right" },
|
|
62
103
|
{ id: "side", label: "SIDE", width: fixed.side, align: "left" },
|
|
@@ -66,14 +107,43 @@ function buildColumns(width: number, dated: boolean): DataTableColumn[] {
|
|
|
66
107
|
];
|
|
67
108
|
}
|
|
68
109
|
|
|
110
|
+
const TIDE_COLUMNS: DataTableColumn[] = [
|
|
111
|
+
{ id: "time", label: "TIME", width: 6, align: "left" },
|
|
112
|
+
{ id: "netCalls", label: "NET CALLS", width: 10, align: "right" },
|
|
113
|
+
{ id: "netPuts", label: "NET PUTS", width: 10, align: "right" },
|
|
114
|
+
{ id: "net", label: "NET PREM", width: 10, align: "right" },
|
|
115
|
+
{ id: "change", label: "CHG", width: 9, align: "right" },
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
const STRIKE_COLUMNS: Array<DataTableColumn & { id: FlowStrikeSort }> = [
|
|
119
|
+
{ id: "expiry", label: "EXP", width: 8, align: "left" },
|
|
120
|
+
{ id: "strike", label: "STRIKE", width: 8, align: "right" },
|
|
121
|
+
{ id: "callPremium", label: "CALLS", width: 8, align: "right" },
|
|
122
|
+
{ id: "callAsk", label: "C ASK", width: 8, align: "right" },
|
|
123
|
+
{ id: "callBid", label: "C BID", width: 8, align: "right" },
|
|
124
|
+
{ id: "putPremium", label: "PUTS", width: 8, align: "right" },
|
|
125
|
+
{ id: "putAsk", label: "P ASK", width: 8, align: "right" },
|
|
126
|
+
{ id: "putBid", label: "P BID", width: 8, align: "right" },
|
|
127
|
+
{ id: "net", label: "NET", width: 8, align: "right" },
|
|
128
|
+
];
|
|
129
|
+
/** The totals go before the side columns that make up the breakdown. */
|
|
130
|
+
const STRIKE_DROP_ORDER: FlowStrikeSort[] = ["putPremium", "callPremium"];
|
|
131
|
+
|
|
69
132
|
function sideColor(side: ScannerFlowEvent["side"]): string {
|
|
70
133
|
if (side === "ask") return colors.positive;
|
|
71
134
|
if (side === "bid") return colors.negative;
|
|
72
135
|
return side === "mid" ? colors.text : colors.textDim;
|
|
73
136
|
}
|
|
74
137
|
|
|
138
|
+
function signedColor(value: number): string {
|
|
139
|
+
return value > 0 ? colors.positive : value < 0 ? colors.negative : colors.textDim;
|
|
140
|
+
}
|
|
141
|
+
|
|
75
142
|
// Stable table adapters so memoized rows survive feed ticks.
|
|
76
143
|
const eventKey = (event: ScannerFlowEvent) => event.id;
|
|
144
|
+
const tideKey = (row: FlowTideRow) => String(row.at);
|
|
145
|
+
const tideDate = (row: FlowTideRow) => new Date(row.at);
|
|
146
|
+
const strikeKey = (row: FlowStrikeRow) => row.id;
|
|
77
147
|
|
|
78
148
|
function renderCell(
|
|
79
149
|
event: ScannerFlowEvent,
|
|
@@ -111,14 +181,84 @@ function renderCell(
|
|
|
111
181
|
}
|
|
112
182
|
}
|
|
113
183
|
|
|
184
|
+
function renderTideCell(row: FlowTideRow, column: DataTableColumn): DataTableCell {
|
|
185
|
+
if (column.id === "time") return { text: formatTideTime(row.at), value: new Date(row.at).toISOString(), color: colors.textDim };
|
|
186
|
+
const value = row[column.id as "netCalls" | "netPuts" | "net" | "change"];
|
|
187
|
+
return {
|
|
188
|
+
text: formatSignedFlowPremium(value),
|
|
189
|
+
value,
|
|
190
|
+
// Coloured by what it means for the name: put buying is bearish, put selling bullish.
|
|
191
|
+
color: signedColor(column.id === "netPuts" ? -value : value),
|
|
192
|
+
...(column.id === "net" ? { attributes: TextAttributes.BOLD } : {}),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function renderStrikeCell(row: FlowStrikeRow, column: DataTableColumn): DataTableCell {
|
|
197
|
+
switch (column.id) {
|
|
198
|
+
case "expiry":
|
|
199
|
+
return { text: formatStrikeExpiry(row.expiry), value: row.expiry, color: colors.textDim };
|
|
200
|
+
case "strike":
|
|
201
|
+
return { text: formatNumber(row.strike, row.strike % 1 === 0 ? 0 : 2), value: row.strike, color: colors.textBright };
|
|
202
|
+
case "net":
|
|
203
|
+
return { text: formatSignedFlowPremium(row.net), value: row.net, color: signedColor(row.net), attributes: TextAttributes.BOLD };
|
|
204
|
+
default: {
|
|
205
|
+
const value = row[column.id as "callPremium" | "callAsk" | "callBid" | "putPremium" | "putAsk" | "putBid"];
|
|
206
|
+
const call = column.id.startsWith("call");
|
|
207
|
+
const total = column.id === "callPremium" || column.id === "putPremium";
|
|
208
|
+
return {
|
|
209
|
+
text: value > 0 ? formatFlowPremium(value) : MISSING_VALUE,
|
|
210
|
+
value,
|
|
211
|
+
color: value <= 0 ? colors.textDim : total ? (call ? colors.positive : colors.negative) : colors.text,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Amounts on the tide's axis, in one unit for the whole axis: `$12M`, `-$4.5M`. */
|
|
218
|
+
function formatPremiumAxis(value: number, domain: CompositeAxisDomain): string {
|
|
219
|
+
const text = formatCompactAxis(value, domain);
|
|
220
|
+
if (text === "0") return "$0";
|
|
221
|
+
return text.startsWith("-") ? `-$${text.slice(1)}` : `$${text}`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** The tide's lines read in the user's own clock, as the TIME columns do. */
|
|
225
|
+
const LOCAL_TIME_ZONE = Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
226
|
+
|
|
227
|
+
function tideSeries(rows: readonly FlowTideRow[], bucketMs: number): ResolvedSeries[] {
|
|
228
|
+
const points = flowTidePoints(rows, bucketMs);
|
|
229
|
+
const line = (id: string, label: string, color: string, value: (point: (typeof points)[number]) => number): ResolvedSeries => ({
|
|
230
|
+
...staticSeries(points.map((point) => scalarPoint(new Date(point.at), value(point))), { id, label, color }),
|
|
231
|
+
timeBasis: { kind: "market", timeZone: LOCAL_TIME_ZONE, cadenceMs: bucketMs },
|
|
232
|
+
observationKind: "market",
|
|
233
|
+
});
|
|
234
|
+
// Named as the table's columns are; the net line is the tide itself.
|
|
235
|
+
return [
|
|
236
|
+
line("flow-net", "Net prem", colors.textBright, (point) => point.net),
|
|
237
|
+
line("flow-net-calls", "Net calls", colors.positive, (point) => point.netCalls),
|
|
238
|
+
line("flow-net-puts", "Net puts", colors.negative, (point) => point.netPuts),
|
|
239
|
+
];
|
|
240
|
+
}
|
|
241
|
+
|
|
114
242
|
function FlowPane({ focused, width, height }: PaneProps) {
|
|
115
243
|
const feed = useFlowFeed();
|
|
116
244
|
const { pinTicker } = usePluginTickerActions();
|
|
245
|
+
const { createPaneFromTemplate } = usePluginAppActions();
|
|
117
246
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
118
247
|
// Publish the selection on this pane, not the portfolio: moving the
|
|
119
248
|
// portfolio's cursor scrolled it to a ticker the user never picked there.
|
|
120
249
|
const [, setCursorSymbol] = usePaneStateValue<string | null>("cursorSymbol", null);
|
|
121
250
|
|
|
251
|
+
// `FLOW NVDA` is one name's flow; plain FLOW is the market.
|
|
252
|
+
const [symbolSetting] = usePaneSettingValue<string>("symbol", "");
|
|
253
|
+
const symbol = flowScopeSymbol(symbolSetting);
|
|
254
|
+
const tabItems = useMemo(() => flowTabs(symbol), [symbol]);
|
|
255
|
+
const [initialTab] = usePaneSettingValue<string>("tab", "prints");
|
|
256
|
+
const [savedTab, setTab] = usePluginPaneState<string>("flow:tab", initialTab);
|
|
257
|
+
const tab: FlowTab = tabItems.find((item) => item.value === savedTab)?.value ?? "prints";
|
|
258
|
+
// Signed out or without Pro, every tab is the same wall.
|
|
259
|
+
const { strip, rows: tabRows } = usePaneTabs(!feed.denied ? { tabs: tabItems, activeValue: tab, onSelect: setTab, focused, dense: true } : null);
|
|
260
|
+
const bodyHeight = Math.max(3, height - tabRows);
|
|
261
|
+
|
|
122
262
|
const [minPremium, setMinPremium] = usePaneSettingValue<FlowMinPremium>("minPremium", DEFAULT_FLOW_FILTERS.minPremium);
|
|
123
263
|
const [side, setSide] = usePaneSettingValue<FlowSide>("side", DEFAULT_FLOW_FILTERS.side);
|
|
124
264
|
const [kind, setKind] = usePaneSettingValue<FlowKind>("kind", DEFAULT_FLOW_FILTERS.kind);
|
|
@@ -128,9 +268,10 @@ function FlowPane({ focused, width, height }: PaneProps) {
|
|
|
128
268
|
|
|
129
269
|
const trackedSymbols = useAppSelector((state) => state.tickers);
|
|
130
270
|
const watchlist = useMemo(
|
|
131
|
-
() => new Set([...trackedSymbols.keys()].map((
|
|
271
|
+
() => new Set([...trackedSymbols.keys()].map((entry) => entry.toUpperCase())),
|
|
132
272
|
[trackedSymbols],
|
|
133
273
|
);
|
|
274
|
+
const scope = useMemo<FlowScope>(() => ({ symbol, watchlist }), [symbol, watchlist]);
|
|
134
275
|
|
|
135
276
|
const filters = useMemo<FlowFilters>(
|
|
136
277
|
() => ({ minPremium, side, kind, volOi, expiry, universe }),
|
|
@@ -146,11 +287,11 @@ function FlowPane({ focused, width, height }: PaneProps) {
|
|
|
146
287
|
return keptRef.current;
|
|
147
288
|
}, [feed.payload?.events]);
|
|
148
289
|
const liveRows = useMemo(
|
|
149
|
-
() => filterFlowEvents(kept, filters,
|
|
150
|
-
[filters, kept,
|
|
290
|
+
() => filterFlowEvents(kept, filters, scope),
|
|
291
|
+
[filters, kept, scope],
|
|
151
292
|
);
|
|
152
293
|
|
|
153
|
-
const historyQuery = useMemo(() => flowHistoryQuery(filters,
|
|
294
|
+
const historyQuery = useMemo(() => flowHistoryQuery(filters, scope), [filters, scope]);
|
|
154
295
|
const oldestKept = kept.at(-1);
|
|
155
296
|
const oldestLive = useMemo(
|
|
156
297
|
() => (oldestKept ? { at: oldestKept.at, id: oldestKept.id } : null),
|
|
@@ -159,32 +300,58 @@ function FlowPane({ focused, width, height }: PaneProps) {
|
|
|
159
300
|
const history = useFlowHistory(historyQuery, oldestLive, !!feed.payload && !feed.denied);
|
|
160
301
|
const events = useMemo(() => mergeFlowRows(liveRows, history.events), [history.events, liveRows]);
|
|
161
302
|
|
|
303
|
+
// The session summary is read only while Tide or Strikes is on screen: it
|
|
304
|
+
// loads when one first opens, follows the filters and refreshes while shown,
|
|
305
|
+
// and holds still behind Prints.
|
|
306
|
+
const sessionShown = tab !== "prints";
|
|
307
|
+
const sessionSearch = useMemo(() => flowSessionSearch(filters, scope), [filters, scope]);
|
|
308
|
+
const [shownSearch, setShownSearch] = useState<string | null>(sessionShown ? sessionSearch : null);
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
if (sessionShown) setShownSearch(sessionSearch);
|
|
311
|
+
}, [sessionSearch, sessionShown]);
|
|
312
|
+
const sessionLoader = useMemo(
|
|
313
|
+
() => (shownSearch === null ? null : () => loadFlowSession(shownSearch)),
|
|
314
|
+
[shownSearch],
|
|
315
|
+
);
|
|
316
|
+
const session = useAsyncResource<ScannerFlowSession>(
|
|
317
|
+
sessionLoader && feed.payload && !feed.denied ? sessionLoader : null,
|
|
318
|
+
);
|
|
319
|
+
useAutoRefresh(session.updatedAt, () => void session.load(), {
|
|
320
|
+
intervalMs: sessionShown && feed.payload?.status === "live" ? FLOW_SESSION_LIVE_REFRESH_MS : null,
|
|
321
|
+
});
|
|
322
|
+
|
|
162
323
|
const scrollRef = useRef<ScrollBoxRenderable | null>(null);
|
|
163
|
-
const canLoadOlder = history.hasMore && !history.loading && !history.error && !!feed.payload && !feed.denied;
|
|
324
|
+
const canLoadOlder = tab === "prints" && history.hasMore && !history.loading && !history.error && !!feed.payload && !feed.denied;
|
|
164
325
|
const onBodyScrollActivity = useTableLoadMore(scrollRef, canLoadOlder, history.loadMore);
|
|
165
326
|
// A strict filter or a quiet tape should still fill the pane, including
|
|
166
327
|
// before the open, when every row is recorded.
|
|
167
|
-
const bodyRows = Math.max(1,
|
|
328
|
+
const bodyRows = Math.max(1, bodyHeight - 3);
|
|
168
329
|
useEffect(() => {
|
|
169
330
|
if (canLoadOlder && events.length < bodyRows) history.loadMore();
|
|
170
331
|
}, [bodyRows, canLoadOlder, events.length, history.loadMore]);
|
|
171
332
|
|
|
172
333
|
const emptyState = useMemo(
|
|
173
|
-
() => flowEmptyState(kept.length, liveRows.length, feed.payload?.status),
|
|
174
|
-
[feed.payload?.status, kept.length, liveRows.length],
|
|
334
|
+
() => flowEmptyState(kept.length, liveRows.length, feed.payload?.status, symbol),
|
|
335
|
+
[feed.payload?.status, kept.length, liveRows.length, symbol],
|
|
175
336
|
);
|
|
176
337
|
|
|
177
|
-
const
|
|
338
|
+
const statusFooter = useMemo<PaneFooterSegment | null>(() => {
|
|
339
|
+
if (tab !== "prints") {
|
|
340
|
+
// A failed refresh keeps the last summary on screen and says so here.
|
|
341
|
+
return session.error && session.data
|
|
342
|
+
? { id: "flow-session", parts: [{ text: "session totals not refreshed", tone: "warning" }] }
|
|
343
|
+
: null;
|
|
344
|
+
}
|
|
178
345
|
if (history.error) {
|
|
179
346
|
return { id: "flow-history", parts: [{ text: "older prints unavailable", tone: "warning" }] };
|
|
180
347
|
}
|
|
181
348
|
if (history.loading) return { id: "flow-history", parts: [{ text: "loading older prints", tone: "muted" }] };
|
|
182
349
|
return null;
|
|
183
|
-
}, [history.error, history.loading]);
|
|
184
|
-
useScannerStatusFooter("flow", feed, focused,
|
|
350
|
+
}, [history.error, history.loading, session.data, session.error, tab]);
|
|
351
|
+
useScannerStatusFooter("flow", feed, focused, statusFooter);
|
|
185
352
|
|
|
186
353
|
const dated = useMemo(() => flowRowsSpanDays(events), [events]);
|
|
187
|
-
const columns = useMemo(() => buildColumns(width, dated), [dated, width]);
|
|
354
|
+
const columns = useMemo(() => buildColumns(width, dated, !symbol), [dated, symbol, width]);
|
|
188
355
|
|
|
189
356
|
const handleRootKeyDown = useCallback((event: DataTableKeyEvent) => {
|
|
190
357
|
if (!isPlainKey(event, "r") || !history.error) return false;
|
|
@@ -193,6 +360,7 @@ function FlowPane({ focused, width, height }: PaneProps) {
|
|
|
193
360
|
history.retry();
|
|
194
361
|
return true;
|
|
195
362
|
}, [history.error, history.retry]);
|
|
363
|
+
usePaneRefreshKey(() => void session.reload(), { focused, enabled: tab !== "prints" && !feed.denied });
|
|
196
364
|
|
|
197
365
|
const handleSelect = useCallback((event: ScannerFlowEvent) => {
|
|
198
366
|
setSelectedId(event.id);
|
|
@@ -203,57 +371,191 @@ function FlowPane({ focused, width, height }: PaneProps) {
|
|
|
203
371
|
return <ScannerDeniedState reason={feed.deniedReason} placement="flow-wall" width={width} height={height} />;
|
|
204
372
|
}
|
|
205
373
|
|
|
374
|
+
const queryBar = (
|
|
375
|
+
<QueryBar
|
|
376
|
+
width={width}
|
|
377
|
+
filters={[
|
|
378
|
+
{ id: "premium", label: "Prem", value: minPremium, defaultValue: DEFAULT_FLOW_FILTERS.minPremium,
|
|
379
|
+
options: FLOW_FILTER_OPTIONS.minPremium, onChange: setMinPremium },
|
|
380
|
+
{ id: "side", label: "Side", value: side, defaultValue: DEFAULT_FLOW_FILTERS.side,
|
|
381
|
+
options: FLOW_FILTER_OPTIONS.side, onChange: setSide },
|
|
382
|
+
{ id: "kind", label: "Kind", value: kind, defaultValue: DEFAULT_FLOW_FILTERS.kind,
|
|
383
|
+
options: FLOW_FILTER_OPTIONS.kind, onChange: setKind },
|
|
384
|
+
{ id: "voloi", label: "V/OI", value: volOi, defaultValue: DEFAULT_FLOW_FILTERS.volOi,
|
|
385
|
+
options: FLOW_FILTER_OPTIONS.volOi, onChange: setVolOi },
|
|
386
|
+
{ id: "expiry", label: "Exp", value: expiry, defaultValue: DEFAULT_FLOW_FILTERS.expiry,
|
|
387
|
+
options: FLOW_FILTER_OPTIONS.expiry, onChange: setExpiry },
|
|
388
|
+
// One name's pane is already narrower than any universe.
|
|
389
|
+
...(symbol ? [] : [{ id: "universe", label: "Univ", value: universe, defaultValue: DEFAULT_FLOW_FILTERS.universe,
|
|
390
|
+
options: FLOW_FILTER_OPTIONS.universe, onChange: setUniverse }]),
|
|
391
|
+
]}
|
|
392
|
+
/>
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
let body: ReactNode;
|
|
396
|
+
if (tab === "tide") {
|
|
397
|
+
body = (
|
|
398
|
+
<FlowTideView
|
|
399
|
+
focused={focused} width={width} height={bodyHeight} query={queryBar}
|
|
400
|
+
session={session.data} loading={session.loading} error={session.error}
|
|
401
|
+
onRetry={session.reload}
|
|
402
|
+
/>
|
|
403
|
+
);
|
|
404
|
+
} else if (tab === "strikes" && symbol) {
|
|
405
|
+
body = (
|
|
406
|
+
<FlowStrikesView
|
|
407
|
+
focused={focused} width={width} height={bodyHeight} query={queryBar} symbol={symbol}
|
|
408
|
+
session={session.data} loading={session.loading} error={session.error}
|
|
409
|
+
onRetry={session.reload}
|
|
410
|
+
onOpenChain={(row) => createPaneFromTemplate("options-pane", { symbol, values: { expiration: row.expiry } })}
|
|
411
|
+
/>
|
|
412
|
+
);
|
|
413
|
+
} else {
|
|
414
|
+
body = (
|
|
415
|
+
<>
|
|
416
|
+
{queryBar}
|
|
417
|
+
<DataTableView<ScannerFlowEvent>
|
|
418
|
+
focused={focused}
|
|
419
|
+
selection={{
|
|
420
|
+
kind: "id",
|
|
421
|
+
selectedId,
|
|
422
|
+
getId: (event) => event.id,
|
|
423
|
+
onChange: (_id, event) => handleSelect(event),
|
|
424
|
+
}}
|
|
425
|
+
rootWidth={width}
|
|
426
|
+
rootHeight={Math.max(2, bodyHeight - 1)}
|
|
427
|
+
columns={columns}
|
|
428
|
+
items={events}
|
|
429
|
+
sortColumnId={null}
|
|
430
|
+
sortDirection="desc"
|
|
431
|
+
getItemKey={eventKey}
|
|
432
|
+
onActivate={(event) => pinTicker(event.underlying, { floating: true, paneType: TICKER_RESEARCH_PANE_ID })}
|
|
433
|
+
renderCell={renderCell}
|
|
434
|
+
selectedTextOverridesCellColor
|
|
435
|
+
emptyContent={
|
|
436
|
+
!feed.payload
|
|
437
|
+
? <PaneStatusBody loading loadingLabel="Waiting for the scanner..." />
|
|
438
|
+
: events.length === 0 && (history.loading || (!!symbol && history.hasMore && !history.error))
|
|
439
|
+
? <PaneStatusBody loading loadingLabel="Loading recorded prints..." />
|
|
440
|
+
: undefined
|
|
441
|
+
}
|
|
442
|
+
emptyStateTitle={emptyState.title}
|
|
443
|
+
emptyStateHint={emptyState.hint}
|
|
444
|
+
onRootKeyDown={handleRootKeyDown}
|
|
445
|
+
onBodyScrollActivity={onBodyScrollActivity}
|
|
446
|
+
scrollRef={scrollRef}
|
|
447
|
+
/>
|
|
448
|
+
</>
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
206
452
|
return (
|
|
207
453
|
<Box flexDirection="column" width={width} height={height}>
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
454
|
+
{strip}
|
|
455
|
+
{body}
|
|
456
|
+
</Box>
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
interface SessionViewProps {
|
|
461
|
+
focused: boolean;
|
|
462
|
+
width: number;
|
|
463
|
+
height: number;
|
|
464
|
+
query: ReactNode;
|
|
465
|
+
session: ScannerFlowSession | null;
|
|
466
|
+
loading: boolean;
|
|
467
|
+
error: string | null;
|
|
468
|
+
onRetry: () => unknown;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** Until the first summary answers, the body loads or says why it failed; after that a failed refresh is the footer's. */
|
|
472
|
+
function sessionStatus(props: SessionViewProps, subject: string) {
|
|
473
|
+
return props.session ? undefined : { loading: props.loading, error: props.error, subject, retry: { onRetry: props.onRetry } };
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* The net-premium tide through the session: call premium bought at the ask
|
|
478
|
+
* less call premium sold at the bid, less the same for puts, bucket by
|
|
479
|
+
* bucket. The chart draws the running totals; the table adds what each
|
|
480
|
+
* bucket moved them by.
|
|
481
|
+
*/
|
|
482
|
+
function FlowTideView(props: SessionViewProps) {
|
|
483
|
+
const { focused, width, height, query, session } = props;
|
|
484
|
+
const rows = useMemo(() => flowTideRows(session), [session]);
|
|
485
|
+
// Newest first, like the prints; the chart keeps every bucket, the table the ones that moved it.
|
|
486
|
+
const tableRows = useMemo(() => flowTideMoves(rows).reverse(), [rows]);
|
|
487
|
+
const bucketMs = session?.bucketMs ?? 300_000;
|
|
488
|
+
const [selected, setSelected] = usePluginPaneState<string | null>("flow:tide-bucket", null);
|
|
489
|
+
const selectedId = tableRows.some((row) => tideKey(row) === selected) ? selected : tableRows[0] ? tideKey(tableRows[0]) : null;
|
|
490
|
+
const link = useChartTableSelection({
|
|
491
|
+
rows: tableRows, getId: tideKey, getDate: tideDate, selectedId, onSelect: setSelected, focused,
|
|
492
|
+
});
|
|
493
|
+
const series = useMemo(() => tideSeries(rows, bucketMs), [bucketMs, rows]);
|
|
494
|
+
const sessionDate = formatFlowSessionDate(session?.session);
|
|
495
|
+
// Two points are not a trend: under three buckets the table has the pane.
|
|
496
|
+
const chart: ChartTableChart | null = rows.length >= 3 ? {
|
|
497
|
+
series,
|
|
498
|
+
formatValue: (value) => formatSignedFlowPremium(value),
|
|
499
|
+
formatAxisValue: formatPremiumAxis,
|
|
500
|
+
viewport: { start: new Date(rows[0]!.at), end: new Date(rows.at(-1)!.at + bucketMs) },
|
|
501
|
+
...(sessionDate ? { legendAccessory: <Text fg={colors.textMuted}>{sessionDate}</Text>, legendAccessoryWidth: sessionDate.length } : {}),
|
|
502
|
+
remoteKind: "flow-tide",
|
|
503
|
+
...link,
|
|
504
|
+
} : !session && props.loading ? { loading: true } : null;
|
|
505
|
+
return (
|
|
506
|
+
<DataTableView<FlowTideRow>
|
|
507
|
+
focused={focused}
|
|
508
|
+
rootWidth={width}
|
|
509
|
+
rootHeight={height}
|
|
510
|
+
columns={TIDE_COLUMNS}
|
|
511
|
+
items={tableRows}
|
|
512
|
+
sortColumnId={null}
|
|
513
|
+
sortDirection="desc"
|
|
514
|
+
getItemKey={tideKey}
|
|
515
|
+
selection={{ kind: "id", selectedId, getId: tideKey, onChange: (id) => setSelected(id) }}
|
|
516
|
+
onActivate={(row) => setSelected(tideKey(row))}
|
|
517
|
+
renderCell={renderTideCell}
|
|
518
|
+
selectedTextOverridesCellColor
|
|
519
|
+
status={sessionStatus(props, "the session's net premium")}
|
|
520
|
+
emptyStateTitle="No prints under these filters this session."
|
|
521
|
+
rootBefore={<ChartTableHeader width={width} height={height} tableRows={tableRows.length} tableColumns={TIDE_COLUMNS} query={query} chart={chart} />}
|
|
522
|
+
/>
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/** One name's session premium by expiry and strike, split by side. Enter opens the chain at that expiry. */
|
|
527
|
+
function FlowStrikesView(props: SessionViewProps & { symbol: string; onOpenChain: (row: FlowStrikeRow) => void }) {
|
|
528
|
+
const { focused, width, height, query, session } = props;
|
|
529
|
+
const [sort, setSort] = usePluginPaneState<{ column: FlowStrikeSort | null; direction: "asc" | "desc" }>(
|
|
530
|
+
"flow:strike-sort", { column: null, direction: "desc" });
|
|
531
|
+
const rows = useMemo(() => sortFlowStrikes(flowStrikeRows(session), sort.column, sort.direction), [session, sort]);
|
|
532
|
+
const columns = useMemo(() => fitChartTableColumns(STRIKE_COLUMNS, width, STRIKE_DROP_ORDER), [width]);
|
|
533
|
+
const [selected, setSelected] = usePluginPaneState<string | null>("flow:strike", null);
|
|
534
|
+
const selectedId = rows.some((row) => row.id === selected) ? selected : rows[0]?.id ?? null;
|
|
535
|
+
return (
|
|
536
|
+
<>
|
|
537
|
+
{query}
|
|
538
|
+
<DataTableView<FlowStrikeRow, DataTableColumn & { id: FlowStrikeSort }>
|
|
226
539
|
focused={focused}
|
|
227
|
-
selection={{
|
|
228
|
-
kind: "id",
|
|
229
|
-
selectedId,
|
|
230
|
-
getId: (event) => event.id,
|
|
231
|
-
onChange: (_id, event) => handleSelect(event),
|
|
232
|
-
}}
|
|
233
540
|
rootWidth={width}
|
|
234
541
|
rootHeight={Math.max(2, height - 1)}
|
|
235
542
|
columns={columns}
|
|
236
|
-
items={
|
|
237
|
-
sortColumnId={
|
|
238
|
-
sortDirection=
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
543
|
+
items={rows}
|
|
544
|
+
sortColumnId={sort.column}
|
|
545
|
+
sortDirection={sort.direction}
|
|
546
|
+
onHeaderClick={(column) => setSort((current) => ({
|
|
547
|
+
column: column as FlowStrikeSort,
|
|
548
|
+
direction: current.column === column && current.direction === "desc" ? "asc" : "desc",
|
|
549
|
+
}))}
|
|
550
|
+
getItemKey={strikeKey}
|
|
551
|
+
selection={{ kind: "id", selectedId, getId: strikeKey, onChange: (id) => setSelected(id) }}
|
|
552
|
+
onActivate={props.onOpenChain}
|
|
553
|
+
renderCell={renderStrikeCell}
|
|
242
554
|
selectedTextOverridesCellColor
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
? <PaneStatusBody loading loadingLabel="Waiting for the scanner..." />
|
|
246
|
-
: events.length === 0 && history.loading
|
|
247
|
-
? <PaneStatusBody loading loadingLabel="Loading recorded prints..." />
|
|
248
|
-
: undefined
|
|
249
|
-
}
|
|
250
|
-
emptyStateTitle={emptyState.title}
|
|
251
|
-
emptyStateHint={emptyState.hint}
|
|
252
|
-
onRootKeyDown={handleRootKeyDown}
|
|
253
|
-
onBodyScrollActivity={onBodyScrollActivity}
|
|
254
|
-
scrollRef={scrollRef}
|
|
555
|
+
status={sessionStatus(props, `${props.symbol} strikes`)}
|
|
556
|
+
emptyStateTitle={`No ${props.symbol} prints under these filters this session.`}
|
|
255
557
|
/>
|
|
256
|
-
|
|
558
|
+
</>
|
|
257
559
|
);
|
|
258
560
|
}
|
|
259
561
|
|