@gloom-sh/terminal 0.18.5 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +163 -8
- package/src/api-client/app-attention.ts +3 -0
- package/src/api-client/beneficial-owners.ts +3 -0
- package/src/api-client/company-kpis.ts +3 -0
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +19 -7
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/hiring.ts +3 -0
- package/src/api-client/index.ts +15 -0
- package/src/api-client/ipo.ts +24 -1
- 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/mna.ts +3 -0
- package/src/api-client/paths.ts +9 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/research-activity.ts +3 -2
- package/src/api-client/roadmap.ts +85 -0
- package/src/api-client/types.ts +82 -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/core-builtin-allowlist.json +2 -2
- 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 +107 -38
- package/src/cli/commands/news-search.ts +22 -0
- package/src/cli/commands/options-chain.ts +48 -7
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +147 -32
- package/src/cli/commands/search.ts +21 -1
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +87 -36
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +25 -2
- package/src/cli/glossary.ts +31 -2
- package/src/cli/history-rows.ts +57 -9
- package/src/cli/index-roots.ts +0 -7
- package/src/cli/index.ts +2 -0
- package/src/cli/listing-arg.ts +235 -60
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +4 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/discovery.ts +21 -1
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +93 -4
- package/src/cli/pane-functions/function-help.ts +20 -10
- package/src/cli/pane-functions/headless.ts +17 -7
- package/src/cli/pane-functions/index.ts +54 -16
- 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/saved-copy-persistence.ts +88 -0
- 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/list/model.ts +5 -0
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/routes/root/runtime.ts +6 -7
- package/src/components/command-bar/selection-runtime.ts +52 -3
- package/src/components/command-bar/surface/index.tsx +2 -0
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/pane/capture-stamp.ts +2 -2
- package/src/components/layout/pane/footer/index.tsx +22 -6
- package/src/components/layout/pane/footer/model.ts +83 -15
- package/src/components/layout/pane/status-footer.ts +35 -2
- package/src/components/layout/status-widget-space.ts +9 -0
- 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 +8 -21
- 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/display.tsx +18 -3
- package/src/components/ui/icon.tsx +4 -1
- 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/plugin-cache.ts +5 -3
- package/src/data/saved-copies.ts +8 -0
- 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 +103 -142
- package/src/i18n/es.ts +20 -6
- package/src/i18n/ja.ts +20 -6
- package/src/i18n/ko.ts +20 -6
- package/src/i18n/zh-cn.ts +20 -6
- package/src/i18n/zh-tw.ts +20 -6
- package/src/market-data/depositary-pair.ts +27 -3
- 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/market-data/quotes/metadata.ts +7 -0
- package/src/news/aggregator.ts +119 -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 +17 -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/calendar-events.ts +10 -4
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +362 -80
- package/src/plugins/builtin/alerts/command.ts +25 -1
- package/src/plugins/builtin/alerts/event-rows.ts +24 -3
- package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
- 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 +34 -12
- 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/cds/client.ts +142 -30
- package/src/plugins/builtin/cds/headless.ts +35 -8
- package/src/plugins/builtin/cds/model.ts +145 -18
- package/src/plugins/builtin/cds/pane.tsx +16 -5
- 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/follow-binding.ts +14 -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 +5 -6
- 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/controller/index.ts +5 -1
- package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
- package/src/plugins/builtin/chat/controller/state.ts +25 -2
- package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
- package/src/plugins/builtin/chat/controller/utils.ts +7 -2
- package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
- package/src/plugins/builtin/chat/status-preview.ts +79 -0
- package/src/plugins/builtin/chat/status-widget.tsx +147 -10
- package/src/plugins/builtin/chat/unread-badge.ts +51 -10
- package/src/plugins/builtin/clinical-trials/client.ts +49 -4
- package/src/plugins/builtin/clinical-trials/model.ts +86 -31
- package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
- package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
- package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
- 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/headless.ts +2 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +12 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/company-kpis/headless.ts +8 -3
- package/src/plugins/builtin/company-kpis/model.ts +73 -5
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/composite-plugins.ts +2 -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 +10 -10
- 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/doe/headless.ts +3 -2
- package/src/plugins/builtin/doe/pane.tsx +13 -3
- 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/data.ts +18 -6
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
- package/src/plugins/builtin/econ/calendar-model.ts +48 -1
- package/src/plugins/builtin/econ/calendar-source.ts +4 -2
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/figures.ts +48 -0
- package/src/plugins/builtin/econ/index.tsx +14 -5
- package/src/plugins/builtin/econ/print-watch.ts +3 -1
- package/src/plugins/builtin/econ/types.ts +2 -0
- package/src/plugins/builtin/econ-statistics/client.ts +11 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
- package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +39 -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 +62 -59
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
- package/src/plugins/builtin/help/table-cells.tsx +13 -2
- package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +22 -3
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/holders/report-rows.ts +4 -1
- package/src/plugins/builtin/insider/headless.ts +2 -2
- package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
- package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
- package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
- 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/headless.ts +12 -5
- package/src/plugins/builtin/macro-day/index.tsx +1 -1
- package/src/plugins/builtin/macro-day/model.ts +39 -5
- package/src/plugins/builtin/macro-day/pane.tsx +27 -19
- 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 +27 -7
- package/src/plugins/builtin/market-movers/headless.ts +34 -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 +22 -4
- 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/mna/headless.ts +2 -0
- package/src/plugins/builtin/mna/pane.tsx +3 -1
- package/src/plugins/builtin/news/headless.ts +3 -1
- package/src/plugins/builtin/news/index.tsx +4 -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 +6 -7
- 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/index.tsx +0 -14
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
- package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/realized-vol/client.ts +13 -5
- package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
- package/src/plugins/builtin/realized-vol/headless.ts +8 -4
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/model.ts +37 -6
- package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
- 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 +40 -12
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/index.tsx +7 -3
- package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
- package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
- package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
- 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/risk-factors/data.ts +4 -2
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/roadmap/index.tsx +28 -0
- package/src/plugins/builtin/roadmap/model.ts +67 -0
- package/src/plugins/builtin/roadmap/pane.tsx +244 -0
- 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 +48 -5
- 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 +4 -2
- package/src/plugins/builtin/sec/index.tsx +3 -0
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
- 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/headless-market-data.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 +11 -2
- package/src/plugins/builtin/shared/research-cells.tsx +1 -1
- package/src/plugins/builtin/shared/series-cache.ts +4 -4
- 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/thirteenf/api.ts +12 -1
- package/src/plugins/builtin/thirteenf/data.ts +7 -7
- package/src/plugins/builtin/thirteenf/headless.ts +31 -4
- 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 +11 -3
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
- 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-auctions/headless.ts +21 -12
- package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
- 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 +7 -4
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/builtin/yield-curve/client.ts +61 -1
- package/src/plugins/builtin/yield-curve/headless.ts +9 -6
- package/src/plugins/builtin/yield-curve/index.tsx +34 -10
- package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/host-link.ts +1 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +11 -7
- 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/icons.tsx +7 -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 +21 -10
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
- 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/bun/desktop/windows-script.ts +92 -0
- package/src/renderers/electrobun/bun/index.ts +26 -2
- package/src/renderers/electrobun/bun/window/create.ts +43 -4
- package/src/renderers/electrobun/bun/window/events.ts +20 -0
- package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
- package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
- package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
- package/src/renderers/electrobun/shared/app-name.ts +13 -0
- package/src/renderers/electrobun/shared/protocol.ts +12 -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 +5 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/ui-host.tsx +8 -0
- 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/fred-series.ts +3 -1
- package/src/sources/gloomberb-cloud/index.ts +43 -8
- 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-listing.ts +53 -13
- package/src/tickers/company-name.ts +50 -4
- package/src/tickers/index-roots.ts +7 -0
- package/src/tickers/listing-fate.ts +2 -2
- package/src/tickers/listing-venues.ts +15 -3
- package/src/tickers/search/index.ts +6 -2
- package/src/tickers/search/ranking.ts +5 -1
- package/src/tickers/search/upsert.ts +13 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/tickers/ticker-changes.ts +8 -4
- 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/company-from.ts +40 -0
- package/src/types/data-provider.ts +7 -0
- package/src/types/financials.ts +59 -1
- package/src/types/headless.ts +27 -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 +165 -7
- package/src/utils/declared-dividends.ts +1 -1
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +60 -8
- 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/version.ts +2 -2
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
|
@@ -5,6 +5,7 @@ import type { KpiObservation } from "../../../api-client/company-kpis";
|
|
|
5
5
|
import { listingAbroad } from "../../../api-client/paths";
|
|
6
6
|
import { ChartTableHeader, DataTableStackView, DetailScrollBody, EmptyState, KeyValueRow, PaneStatusBody, QueryBar, SectionHeading,
|
|
7
7
|
useChartTableSelection, usePaneMenuItems, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, type DataTableCell, type PaneHint } from "../../../components";
|
|
8
|
+
import { useCompanyDataFooter } from "../../../components/layout/pane/status-footer";
|
|
8
9
|
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
9
10
|
import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginAppActions, usePluginPaneState } from "../../../public/react";
|
|
10
11
|
import { usePaneTickerIdentity } from "../../../state/hooks/pane-ticker";
|
|
@@ -20,7 +21,7 @@ import { Blurred, LockedOverlay, UpgradeLabel } from "../shared/locked-rows";
|
|
|
20
21
|
import { isCloudSessionRequired, useResearchCloudSession } from "../shared/research-cloud-session";
|
|
21
22
|
import { cachedCompanyData, loadCompanyData, KPI_UNAVAILABLE, validateCompanyData, type CompanyDataset, type CompanyMode } from "./client";
|
|
22
23
|
import { useCompanyEvidence } from "./screenshot-evidence";
|
|
23
|
-
import { allGuidance, allObservations, basisLabel, changeText, changeTone, companyCellText, companyColumns, dimensionLabel, directionLabel, directionTone, guideGroupKey, guidanceSeries, fiscalLabel,
|
|
24
|
+
import { allGuidance, allObservations, basisLabel, changeText, changeTone, companyCellText, companyColumns, companyEmptyState, dimensionLabel, directionLabel, directionTone, fitCompanyColumns, guideGroupKey, guidanceSeries, fiscalLabel,
|
|
24
25
|
isGuidance, exactObservation, observationValue, observationChange, observationChart, periodOrder, outcomeLabel, QUIET_COLUMNS, rangeText, scopeWords, seriesDetail, seriesLabel, seriesLabels, seriesName,
|
|
25
26
|
seriesOrder, unitLabel, valueText, companyPreviewNote, type CompanyRow } from "./model";
|
|
26
27
|
import { PriceSparkline } from "../../../components/price-sparkline/view";
|
|
@@ -47,7 +48,7 @@ function DisclosureEvidence({ row, sourceId, onSource, width }: { row: CompanyRo
|
|
|
47
48
|
<KeyValueRow label="Revision" value={`${row.revision}${row.current ? " · current" : " · superseded"}${!isGuidance(row) && (row.conflict || row.contested) ? " · conflicting disclosures" : ""}`} labelWidth={18} />
|
|
48
49
|
{row.revisionReason ? <KeyValueRow label="Correction" value={row.revisionReason} labelWidth={18} /> : null}
|
|
49
50
|
{isGuidance(row) ? <>
|
|
50
|
-
<KeyValueRow label="Issued" value={row.issuedDate} detail={directionLabel(row
|
|
51
|
+
<KeyValueRow label="Issued" value={row.issuedDate} detail={directionLabel(row)} labelWidth={18} />
|
|
51
52
|
<KeyValueRow label="Source range" value={row.rangeText} labelWidth={18} />
|
|
52
53
|
{row.conditions ? <KeyValueRow label="Conditions" value={row.conditions} labelWidth={18} /> : null}
|
|
53
54
|
<KeyValueRow label="Later actual" value={row.actual ? valueText(row, row.actual.value) : "Pending"} detail={row.actual ? outcomeLabel(row) : undefined} labelWidth={18} />
|
|
@@ -130,7 +131,7 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
|
|
|
130
131
|
: sort.column === "actual" ? guide?.actual?.value ?? null : sort.column === "outcome" ? guide ? outcomeLabel(guide) : null
|
|
131
132
|
: sort.column === "basis" ? row.basis : sort.column === "unit" ? unitLabel(row) : sort.column === "scope" ? dimensionLabel(row.dimensions)
|
|
132
133
|
: sort.column === "published" ? row.evidence[0]!.publishedAt : sort.column === "revision" ? row.revision
|
|
133
|
-
: sort.column === "quote" ? row.evidence[0]!.quote : sort.column === "change" ? guide ? guide
|
|
134
|
+
: sort.column === "quote" ? row.evidence[0]!.quote : sort.column === "change" ? guide ? directionLabel(guide) : observationChange(row as KpiObservation, observations).value
|
|
134
135
|
: periodOrder(row);
|
|
135
136
|
};
|
|
136
137
|
const direction = sort.direction === "asc" ? 1 : -1;
|
|
@@ -158,8 +159,13 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
|
|
|
158
159
|
}
|
|
159
160
|
return map;
|
|
160
161
|
}, [kpis]);
|
|
161
|
-
const columns = useMemo(() =>
|
|
162
|
-
|
|
162
|
+
const columns = useMemo(() => {
|
|
163
|
+
const shown = withoutQuietColumns(companyColumns(mode === "guidance", tab === "evidence", tab === "history", mode === "kpis" && tab === "table" && trends.size > 0 && width >= 100, desktop),
|
|
164
|
+
rows, (row, id) => id === "basis" ? basisLabel(row.basis) : id === "scope" ? dimensionLabel(row.dimensions) || "Consolidated" : "", QUIET_COLUMNS);
|
|
165
|
+
// Evidence is a wide ledger that scrolls; the other tabs fit the pane.
|
|
166
|
+
return tab === "evidence" ? shown : fitCompanyColumns(shown, width, tab === "history");
|
|
167
|
+
}, [mode, tab, trends, rows, width, desktop]);
|
|
168
|
+
const emptyState = companyEmptyState(mode, symbol ?? "", { tab, hasRows: allRows.length > 0 });
|
|
163
169
|
const metricOptions = useMemo(() => [{ value: "all", label: "All metrics" }, ...[...new Map(allRows.map((row) => [row.metricId, { value: row.metricId, label: row.metric.name }])).values()]], [allRows]);
|
|
164
170
|
const kpiChart = useMemo(() => observationChart(rows.filter((row): row is KpiObservation => !isGuidance(row)), colors.warning), [rows, colors.warning]);
|
|
165
171
|
const series = useMemo(() => tab !== "chart" ? [] : mode === "kpis" ? kpiChart.series
|
|
@@ -190,6 +196,8 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
|
|
|
190
196
|
usePaneNoticeFooter({ registrationId: "company-kpis:notices", focused, notices: [resource.data?.refreshError,
|
|
191
197
|
data?.truncated && data.access === "full" ? "The response reached its stored-record limit. Narrow the metric or date range in a CLI or REST query." : null,
|
|
192
198
|
data?.coverage.conflicts ? "Conflicting disclosures are retained in Evidence and excluded from comparable trends." : null].filter((value): value is string => !!value) });
|
|
199
|
+
// A receipt's or foreign line's disclosures are its company's, read from the home line.
|
|
200
|
+
useCompanyDataFooter(data?.companyFrom);
|
|
193
201
|
if (!symbol) return <EmptyState title="Select a ticker." />;
|
|
194
202
|
if (signInWall) return <SignInWall placement="company-kpis-signin" action={mode === "guidance" ? "view company guidance" : "view company KPIs"} needsVerification={session.needsVerification} />;
|
|
195
203
|
const bodyHeight = Math.max(3, height - tabRows);
|
|
@@ -212,7 +220,7 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
|
|
|
212
220
|
return { text: "", content: <PriceSparkline priceHistory={points} width={column.width} period="all" trend={tone === "muted" ? "neutral" : tone} /> };
|
|
213
221
|
}
|
|
214
222
|
if (column.id === "change") {
|
|
215
|
-
if (guide) return { text: directionLabel(guide
|
|
223
|
+
if (guide) return { text: directionLabel(guide), color: toneColor(directionTone(guide), colors), keepColorWhenSelected: guide.direction === "raised" || guide.direction === "cut" };
|
|
216
224
|
const change = observationChange(row as KpiObservation, observations);
|
|
217
225
|
return { text: changeText(change), value: change.value, color: toneColor(changeTone(row as KpiObservation, change.value), colors) };
|
|
218
226
|
}
|
|
@@ -244,7 +252,7 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
|
|
|
244
252
|
detailScrollRef={detailScrollRef} detailContent={openRow ? <DetailScrollBody ref={detailScrollRef} resetScrollKey={openRow.id}><DisclosureEvidence row={openRow} sourceId={sourceId} onSource={setSource} width={width} /></DetailScrollBody> : null}
|
|
245
253
|
sortColumnId={sort.column} sortDirection={sort.direction} onHeaderClick={(column) => setSort((previous) => ({ column, direction: previous.column === column && previous.direction === "desc" ? "asc" : "desc" }))}
|
|
246
254
|
resetScrollKey={`${mode}:${symbol}:${tab}:${metric}:${activePlot}`}
|
|
247
|
-
emptyStateTitle={
|
|
255
|
+
emptyStateTitle={emptyState.title} emptyStateHint={emptyState.hint}
|
|
248
256
|
rootBefore={<ChartTableHeader width={width} height={bodyHeight} tableRows={items.length} tableColumns={columns} query={query}
|
|
249
257
|
chart={tab === "chart" ? { series, ...(mode === "kpis" ? { xAxis: kpiChart.xAxis, viewport: kpiChart.viewport } : {}), empty: preview ? "Full history is available with Pro." : "History is accumulating. Two comparable disclosures are needed for a trend.",
|
|
250
258
|
formatValue: (value) => selected ? `${valueText(selected, value)}${selected.currency ? ` ${unitLabel(selected)}` : ""}` : String(value),
|
|
@@ -14,6 +14,7 @@ import { portfolioAnalyticsModule } from "./analytics";
|
|
|
14
14
|
import { brokerTradingModule } from "./broker-trading";
|
|
15
15
|
import { brokerManagerModule } from "./broker-manager";
|
|
16
16
|
import { changelogModule } from "./changelog";
|
|
17
|
+
import { roadmapModule } from "./roadmap";
|
|
17
18
|
import { connectionsModule } from "./connections";
|
|
18
19
|
import { pluginMarketplaceModule } from "./plugin-marketplace";
|
|
19
20
|
import { relativeRotationModule } from "./relative-rotation";
|
|
@@ -116,7 +117,7 @@ import { FIXED_INCOME_HOSTS } from "./fixed-income/model";
|
|
|
116
117
|
|
|
117
118
|
export const applicationPlugin = composeBuiltinPlugin({
|
|
118
119
|
...applicationPluginMeta,
|
|
119
|
-
modules: [layoutManagerModule, pluginMarketplaceModule, helpModule, changelogModule, connectionsModule, docsModule],
|
|
120
|
+
modules: [layoutManagerModule, pluginMarketplaceModule, helpModule, changelogModule, connectionsModule, docsModule, roadmapModule],
|
|
120
121
|
});
|
|
121
122
|
|
|
122
123
|
export const portfolioPlugin = composeBuiltinPlugin({
|
|
@@ -8,8 +8,9 @@ import { buildCorrelationMatrix, buildCorrelationSeries, buildGeoCorrelationSeri
|
|
|
8
8
|
import { geoSeriesToken, loadGeoCorrelationHistory } from "./geo";
|
|
9
9
|
import { buildRelationshipAnalysis, DEFAULT_RELATIONSHIP_SECOND_SYMBOL } from "./relationship/model";
|
|
10
10
|
import { paneSchemas } from "./headless-schema";
|
|
11
|
-
import { CORRELATION_RETURN_BASIS, GEO_CORRELATION_CHANGE_BASIS, loadCorrelationHistory } from "./history";
|
|
11
|
+
import { CORRELATION_RETURN_BASIS, GEO_CORRELATION_CHANGE_BASIS, loadCorrelationHistory, unfinishedBarFreshness } from "./history";
|
|
12
12
|
import type { HeadlessPaneContext } from "../../../types/headless";
|
|
13
|
+
import type { PricePoint } from "../../../types/financials";
|
|
13
14
|
import { newestReportTime } from "../../../utils/utc-time";
|
|
14
15
|
import { DAILY_CLOSES } from "../shared/report-freshness";
|
|
15
16
|
|
|
@@ -34,9 +35,13 @@ async function loadListedHistory(ctx: HeadlessPaneContext, key: string, range: T
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
/** A ticker's daily closes, or a map series' daily values read straight from the Cloud client. */
|
|
37
|
-
async function loadCorrelationInput(ctx: HeadlessPaneContext, key: string, range: TimeRange): Promise<CorrelationSeries> {
|
|
38
|
+
async function loadCorrelationInput(ctx: HeadlessPaneContext, key: string, range: TimeRange, onBars?: (bars: PricePoint[]) => void): Promise<CorrelationSeries> {
|
|
38
39
|
const geo = geoSeriesToken(key);
|
|
39
|
-
if (!geo)
|
|
40
|
+
if (!geo) {
|
|
41
|
+
const bars = await loadHistory(ctx, key, range);
|
|
42
|
+
onBars?.(bars);
|
|
43
|
+
return buildCorrelationSeries(key, bars);
|
|
44
|
+
}
|
|
40
45
|
const request = <T,>(path: string, init?: RequestInit) => ctx.apiClient.geo<T>(path, { ...init, signal: init?.signal ?? ctx.signal });
|
|
41
46
|
return buildGeoCorrelationSeries(key, await loadGeoCorrelationHistory(request, geo, range, ctx.signal));
|
|
42
47
|
}
|
|
@@ -53,7 +58,8 @@ export const correlationHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
53
58
|
],
|
|
54
59
|
async load({ symbols, options }, ctx) {
|
|
55
60
|
const range = (options.rangePreset ?? "1Y") as TimeRange;
|
|
56
|
-
const
|
|
61
|
+
const bars = new Map<string, PricePoint[]>();
|
|
62
|
+
const loaded = await loadHeadlessSymbols(symbols, ctx, (symbol) => loadCorrelationInput(ctx, symbol, range, (points) => bars.set(symbol, points)));
|
|
57
63
|
const bySymbol = new Map(loaded.entries.map(({ symbol, data }) => [symbol, data]));
|
|
58
64
|
const matrix = buildCorrelationMatrix(symbols, bySymbol);
|
|
59
65
|
const rows = symbols.flatMap((left, index) => symbols.slice(index + 1).map((right) => {
|
|
@@ -68,8 +74,9 @@ export const correlationHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
68
74
|
? "Inconsistent OHLC history"
|
|
69
75
|
: row.sampleSize < 5 ? "Insufficient shared return observations" : "Zero return variance",
|
|
70
76
|
}));
|
|
77
|
+
const asOf = newestReportTime(symbols.map((symbol) => bySymbol.get(symbol)?.prices.at(-1)?.dateKey));
|
|
71
78
|
return {
|
|
72
|
-
freshness: { asOf
|
|
79
|
+
freshness: { asOf, ...unfinishedBarFreshness([...bars.values()], asOf?.slice(0, 10)) },
|
|
73
80
|
rows, unavailableSymbols,
|
|
74
81
|
errors: [
|
|
75
82
|
...loaded.errors,
|
|
@@ -132,6 +139,7 @@ export const relationshipHeadless: HeadlessPaneDefinition<"series"> = {
|
|
|
132
139
|
})),
|
|
133
140
|
{ key: "returnCount", label: "Shared returns", value: analysis.stats?.sampleSize ?? analysis.returns.length },
|
|
134
141
|
],
|
|
142
|
+
freshness: unfinishedBarFreshness([...histories.values()], analysis.aligned.at(-1)?.dateKey) ?? {},
|
|
135
143
|
unavailableSymbols: [...new Set(unavailableSymbols)],
|
|
136
144
|
errors: [...loaded.errors, ...(analysis.unavailableReason ? [analysis.unavailableReason] : []),
|
|
137
145
|
...(analysis.correlationUnavailableReason ? [analysis.correlationUnavailableReason] : [])],
|
|
@@ -2,6 +2,9 @@ import type { AssetDataProvider, MarketDataRequestContext } from "../../../types
|
|
|
2
2
|
import type { TimeRange } from "../../../time-series/range";
|
|
3
3
|
import type { ManualChartResolution } from "../../../time-series/resolution";
|
|
4
4
|
import { clipPriceHistoryToRange } from "../../../time-series/history-window";
|
|
5
|
+
import { DAY_SO_FAR_TEXT } from "../../../time-series/session-bar";
|
|
6
|
+
import type { PricePoint } from "../../../types/financials";
|
|
7
|
+
import type { HeadlessPaneFreshness } from "../../../types/headless";
|
|
5
8
|
|
|
6
9
|
/** Risk views need daily closes even when a chart preset defaults to intraday or weekly bars. */
|
|
7
10
|
export const CORRELATION_HISTORY_RESOLUTION: ManualChartResolution = "1d";
|
|
@@ -16,6 +19,25 @@ export async function loadCorrelationHistory(
|
|
|
16
19
|
return clipPriceHistoryToRange(points, range);
|
|
17
20
|
}
|
|
18
21
|
|
|
22
|
+
/**
|
|
23
|
+
* What an analysis of daily closes says when its newest day is a bar the source
|
|
24
|
+
* has not finished: a coin's UTC day or a future's session under way
|
|
25
|
+
* (`partial`), or a future's last trade before its settlement (`unsettled`).
|
|
26
|
+
* That day is no close, so the line calls the bars daily bars and says so, as
|
|
27
|
+
* `history` does, instead of dating a close by it. Nothing when `day` (the
|
|
28
|
+
* newest date the analysis uses) is a finished bar in every history.
|
|
29
|
+
*/
|
|
30
|
+
export function unfinishedBarFreshness(
|
|
31
|
+
histories: ReadonlyArray<ReadonlyArray<Pick<PricePoint, "date" | "partial" | "unsettled">> | undefined>,
|
|
32
|
+
day: string | null | undefined,
|
|
33
|
+
): Pick<HeadlessPaneFreshness, "basis" | "note"> | null {
|
|
34
|
+
if (!day) return null;
|
|
35
|
+
const bars = histories.flatMap((history) => history?.filter((point) => new Date(point.date).toISOString().slice(0, 10) === day) ?? []);
|
|
36
|
+
if (bars.some((bar) => bar.partial)) return { basis: "daily bars", note: `the newest bar is ${DAY_SO_FAR_TEXT}` };
|
|
37
|
+
if (bars.some((bar) => bar.unsettled)) return { basis: "daily bars", note: "the newest bar is not yet settled" };
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
19
41
|
export const CORRELATION_RETURN_BASIS = "Local-price close-to-close returns between shared UTC dates; cash distributions and FX conversion are excluded, and exchange closing times may differ.";
|
|
20
42
|
|
|
21
43
|
export const GEO_CORRELATION_CHANGE_BASIS = "Map series (GEO:) use the change in daily value between the same shared UTC dates, because counts such as transits can be zero.";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { apiClient } from "../../../api-client";
|
|
2
|
-
import type { CotBoardPayload, CotClass, CotClassSummary, CotContractPayload, CotFamily, CotPayloadBase } from "../../../api-client/cot";
|
|
2
|
+
import type { CotBoardPayload, CotClass, CotClassSummary, CotContractPayload, CotFamily, CotPayloadBase, CotPositions } from "../../../api-client/cot";
|
|
3
3
|
import { createPluginCache } from "../../../data/plugin-cache";
|
|
4
4
|
import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
5
5
|
import type { PricePoint } from "../../../types/financials";
|
|
@@ -12,8 +12,8 @@ const date = (value: unknown): value is string => typeof value === "string" && /
|
|
|
12
12
|
const number = (value: unknown) => value === null || typeof value === "number" && Number.isFinite(value);
|
|
13
13
|
const count = (value: unknown) => value === null || typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
14
14
|
const code = (value: unknown) => typeof value === "string" && /^[0-9A-Z]{5}[0-9A-Z+]$/.test(value);
|
|
15
|
-
function base(data: CotPayloadBase, family: CotFamily) {
|
|
16
|
-
if (!data || data.source !== "CFTC" || data.scope !==
|
|
15
|
+
function base(data: CotPayloadBase, family: CotFamily, positions: CotPositions) {
|
|
16
|
+
if (!data || data.source !== "CFTC" || data.scope !== positions || data.reportFamily !== family
|
|
17
17
|
|| !Array.isArray(data.gaps) || !data.gaps.every((gap) => typeof gap === "string")
|
|
18
18
|
|| !Array.isArray(data.classes) || data.classes.some((row) => !COT_CLASSES[family].some((option) => option.value === row.id))
|
|
19
19
|
|| data.asOf !== null && !date(data.asOf) || !Number.isFinite(Date.parse(data.generatedAt))
|
|
@@ -33,8 +33,8 @@ function summary(row: CotClassSummary, family: CotFamily) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
function validateCotBoard(data: CotBoardPayload, family: CotFamily, traderClass: CotClass) {
|
|
37
|
-
base(data, family);
|
|
36
|
+
function validateCotBoard(data: CotBoardPayload, family: CotFamily, traderClass: CotClass, positions: CotPositions) {
|
|
37
|
+
base(data, family, positions);
|
|
38
38
|
if (!Array.isArray(data.rows) || data.traderClass !== traderClass) throw new Error("The server returned an invalid COT board");
|
|
39
39
|
const seen = new Set<string>();
|
|
40
40
|
for (const row of data.rows) {
|
|
@@ -44,8 +44,8 @@ function validateCotBoard(data: CotBoardPayload, family: CotFamily, traderClass:
|
|
|
44
44
|
}
|
|
45
45
|
return data;
|
|
46
46
|
}
|
|
47
|
-
export function validateCotContract(data: CotContractPayload, family: CotFamily, contractCode: string) {
|
|
48
|
-
base(data, family);
|
|
47
|
+
export function validateCotContract(data: CotContractPayload, family: CotFamily, contractCode: string, positions: CotPositions = "futures-only") {
|
|
48
|
+
base(data, family, positions);
|
|
49
49
|
if (data.contract !== null && data.contract.contractCode !== contractCode || !Array.isArray(data.positions) || !Array.isArray(data.history)) {
|
|
50
50
|
throw new Error("The server returned an invalid COT contract");
|
|
51
51
|
}
|
|
@@ -63,30 +63,34 @@ export function validateCotContract(data: CotContractPayload, family: CotFamily,
|
|
|
63
63
|
return data;
|
|
64
64
|
}
|
|
65
65
|
const unavailable = (error: unknown) => unavailableOnServer(error, "COT history is not available yet.", [404, 503]);
|
|
66
|
-
export async function fetchCotBoard(family: CotFamily, traderClass: CotClass, client: Pick<typeof apiClient, "getCloudCotBoard"> = apiClient) {
|
|
67
|
-
try { return validateCotBoard(await client.getCloudCotBoard(family, traderClass), family, traderClass); }
|
|
66
|
+
export async function fetchCotBoard(family: CotFamily, traderClass: CotClass, client: Pick<typeof apiClient, "getCloudCotBoard"> = apiClient, positions: CotPositions = "futures-only") {
|
|
67
|
+
try { return validateCotBoard(await client.getCloudCotBoard(family, traderClass, positions), family, traderClass, positions); }
|
|
68
68
|
catch (error) { throw unavailable(error); }
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
// Futures-only boards keep the key they always had, so a cached board still opens.
|
|
71
|
+
const cotBoardKey = (family: CotFamily, traderClass: CotClass, positions: CotPositions) =>
|
|
72
|
+
positions === "combined" ? `${family}:${traderClass}:combined` : `${family}:${traderClass}`;
|
|
71
73
|
const presentCotBoard = (payload: CotBoardPayload, stale: boolean, refreshError: string | null) => ({
|
|
72
74
|
...payload, gaps: [...payload.gaps, ...(refreshError ? [refreshError] : [])],
|
|
73
75
|
status: stale && payload.status === "available" ? "partial" as const : payload.status,
|
|
74
76
|
});
|
|
75
|
-
export function cachedCotBoard(family: CotFamily, traderClass: CotClass) {
|
|
76
|
-
const cached = cachedCloudResource(cotBoardCache, cotBoardKey(family, traderClass), (payload) => validateCotBoard(payload, family, traderClass));
|
|
77
|
+
export function cachedCotBoard(family: CotFamily, traderClass: CotClass, positions: CotPositions = "futures-only") {
|
|
78
|
+
const cached = cachedCloudResource(cotBoardCache, cotBoardKey(family, traderClass, positions), (payload) => validateCotBoard(payload, family, traderClass, positions));
|
|
77
79
|
return cached ? presentCotBoard(cached.payload, false, null) : null;
|
|
78
80
|
}
|
|
79
|
-
export async function loadCotBoard(family: CotFamily, traderClass: CotClass, force = false) {
|
|
80
|
-
const { payload, stale, refreshError } = await loadCloudResource(cotBoardCache, cotBoardKey(family, traderClass
|
|
81
|
+
export async function loadCotBoard(family: CotFamily, traderClass: CotClass, force = false, positions: CotPositions = "futures-only") {
|
|
82
|
+
const { payload, stale, refreshError } = await loadCloudResource(cotBoardCache, cotBoardKey(family, traderClass, positions),
|
|
83
|
+
() => fetchCotBoard(family, traderClass, apiClient, positions), { force });
|
|
81
84
|
return presentCotBoard(payload, stale, refreshError);
|
|
82
85
|
}
|
|
83
|
-
export async function fetchCotContract(contractCode: string, family: CotFamily, client: Pick<typeof apiClient, "getCloudCotContract"> = apiClient) {
|
|
84
|
-
try { return validateCotContract(await client.getCloudCotContract(contractCode, family), family, contractCode); }
|
|
86
|
+
export async function fetchCotContract(contractCode: string, family: CotFamily, client: Pick<typeof apiClient, "getCloudCotContract"> = apiClient, positions: CotPositions = "futures-only") {
|
|
87
|
+
try { return validateCotContract(await client.getCloudCotContract(contractCode, family, positions), family, contractCode, positions); }
|
|
85
88
|
catch (error) { throw unavailable(error); }
|
|
86
89
|
}
|
|
87
90
|
export interface CotDetailData { payload: CotContractPayload; price: PricePoint[]; priceSymbol: string | null; priceAsOf: string | null; priceWarning: string | null }
|
|
88
|
-
export async function loadCotDetail(contractCode: string, family: CotFamily, client: Pick<typeof apiClient, "getCloudCotContract" | "getCloudHistory"> = apiClient
|
|
89
|
-
|
|
91
|
+
export async function loadCotDetail(contractCode: string, family: CotFamily, client: Pick<typeof apiClient, "getCloudCotContract" | "getCloudHistory"> = apiClient,
|
|
92
|
+
positions: CotPositions = "futures-only"): Promise<CotDetailData> {
|
|
93
|
+
const payload = await fetchCotContract(contractCode, family, client, positions);
|
|
90
94
|
const mapping = cotPriceMapping(contractCode);
|
|
91
95
|
// An unmapped market simply has no price panel; that is not a data failure.
|
|
92
96
|
if (!mapping?.priceSymbol) return { payload, price: [], priceSymbol: null, priceAsOf: null, priceWarning: null };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CotClass, CotClassSummary, CotContractPayload, CotHistoryPoint } from "../../../api-client/cot";
|
|
2
2
|
import type { HeadlessPaneColumn, HeadlessPaneDefinition } from "../../../types/plugin";
|
|
3
3
|
import { fetchCotBoard, fetchCotContract } from "./client";
|
|
4
|
-
import { COT_MAJOR_CODES, COT_SCOPES, cotClass, cotContractCode, cotContractUnitText, cotInteger, cotMarketName, cotScope, cotUnknownMarketMessage } from "./model";
|
|
4
|
+
import { COT_CLASSES, COT_FAMILIES, COT_MAJOR_CODES, COT_POSITIONS, COT_SCOPES, cotClass, cotContractCode, cotContractUnitText, cotFamily, cotInteger, cotMarketName, cotPositions, cotPositionsText, cotScope, cotUnknownMarketMessage } from "./model";
|
|
5
5
|
import { COMMODITIES_VIEW_STATE } from "../commodities-energy/view-state";
|
|
6
6
|
import { REPORT_ROWS } from "../shared/headless-options";
|
|
7
7
|
|
|
@@ -24,9 +24,11 @@ function cotPositionRow(row: CotClassSummary) {
|
|
|
24
24
|
*/
|
|
25
25
|
function cotContractTitle(data: CotContractPayload, code: string): string {
|
|
26
26
|
const units = cotContractUnitText(data.contract?.contractUnits);
|
|
27
|
-
return [data.contract ? cotMarketName(data.contract.marketName) : code, data.asOf ? `report date ${data.asOf}` : null, units
|
|
28
|
-
.filter(Boolean).join(" · ");
|
|
27
|
+
return [data.contract ? cotMarketName(data.contract.marketName) : code, data.asOf ? `report date ${data.asOf}` : null, units,
|
|
28
|
+
data.scope === "combined" ? cotPositionsText(data.scope) : null].filter(Boolean).join(" · ");
|
|
29
29
|
}
|
|
30
|
+
/** Every trader class of every report, each once, in the reports' order. */
|
|
31
|
+
const TRADER_CLASSES = [...new Set(Object.values(COT_CLASSES).flat().map(({ value }) => value))];
|
|
30
32
|
|
|
31
33
|
/** The weekly reports newest first, the newest `limit` of them when given. */
|
|
32
34
|
function cotWeeklyRows(history: readonly CotHistoryPoint[], limit: number | null): CotHistoryPoint[] {
|
|
@@ -47,22 +49,25 @@ export const cotHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
47
49
|
// Tuesday positions published the Friday after; two weeks without a new report means one was missed.
|
|
48
50
|
freshness: { source: "CFTC", status: "not-a-feed", basis: "weekly report", maxAgeMinutes: 14 * 24 * 60 },
|
|
49
51
|
argument: { kind: "free-text", optional: true, placeholder: "code or name", description: "Futures root (KC), commodity name (coffee) or CFTC market code; omit for the extremes board." },
|
|
50
|
-
options: [{ key: "report", type: "enum", values:
|
|
51
|
-
{ key: "traderClass", type: "enum", values:
|
|
52
|
+
options: [{ key: "report", type: "enum", values: COT_FAMILIES.map(({ value }) => ({ value })), defaultValue: "legacy", description: "CFTC report: legacy, disaggregated, or tff (Traders in Financial Futures)." },
|
|
53
|
+
{ key: "traderClass", type: "enum", values: TRADER_CLASSES.map((value) => ({ value })), description: "Trader class for the extremes board; defaults by report family.",
|
|
52
54
|
pluginState: { pluginId: COMMODITIES_VIEW_STATE.cot, key: "class" } },
|
|
55
|
+
{ key: "positions", type: "enum", values: COT_POSITIONS.map(({ value }) => ({ value })), defaultValue: "futures-only", description: "Futures positions only, or futures and options combined, as in the pane.",
|
|
56
|
+
pluginState: { pluginId: COMMODITIES_VIEW_STATE.cot, key: "positions" } },
|
|
53
57
|
{ key: "scope", type: "enum", values: COT_SCOPES.map(({ value }) => ({ value })), defaultValue: "major", description: "Markets on the extremes board, as in the pane.",
|
|
54
58
|
pluginState: { pluginId: COMMODITIES_VIEW_STATE.cot, key: "scope" } },
|
|
55
59
|
// Weekly reports come newest first, so --tail asks for the same newest n as --limit.
|
|
56
60
|
{ key: "limit", type: "integer", aliases: ["tail", "weeks", "rows"], minimum: 1, maximum: 2000, shot: REPORT_ROWS,
|
|
57
61
|
description: "Newest weekly reports a market lists; every one by default." }],
|
|
58
|
-
discovery: { aliases: ["COT", "CFTC"], dataRequirements: ["Gloom Cloud CFTC history"], limitations: ["
|
|
62
|
+
discovery: { aliases: ["COT", "CFTC"], dataRequirements: ["Gloom Cloud CFTC history"], limitations: ["Positions aggregate all maturities; combined adds options, delta-adjusted.", "Legacy, disaggregated and TFF classes differ.", "Observation date is not publication date.", "Some markets have short or suppressed histories."] },
|
|
59
63
|
describe: "CFTC positioning",
|
|
60
64
|
async load(args, ctx) {
|
|
61
|
-
const family = args.options.report
|
|
65
|
+
const family = cotFamily(args.options.report);
|
|
66
|
+
const positions = cotPositions(args.options.positions);
|
|
62
67
|
if (args.argument) {
|
|
63
68
|
const code = cotContractCode(args.argument);
|
|
64
69
|
if (!code) throw new Error(cotUnknownMarketMessage(args.argument));
|
|
65
|
-
const data = await fetchCotContract(code, family, ctx.apiClient);
|
|
70
|
+
const data = await fetchCotContract(code, family, ctx.apiClient, positions);
|
|
66
71
|
const classes = data.positions.map((row) => row.id);
|
|
67
72
|
const limit = args.options.limit == null ? null : Number(args.options.limit);
|
|
68
73
|
const weeks = cotWeeklyRows(data.history, limit);
|
|
@@ -80,9 +85,9 @@ export const cotHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
80
85
|
const traderClass = cotClass(family, args.options.traderClass);
|
|
81
86
|
if (args.options.traderClass && traderClass !== args.options.traderClass) throw new Error("Trader class does not belong to this report family");
|
|
82
87
|
const scope = cotScope(args.options.scope);
|
|
83
|
-
const data = await fetchCotBoard(family, traderClass, ctx.apiClient);
|
|
88
|
+
const data = await fetchCotBoard(family, traderClass, ctx.apiClient, positions);
|
|
84
89
|
const rows = data.rows.filter((row) => scope === "all" || COT_MAJOR_CODES.has(row.contractCode));
|
|
85
|
-
return { sections: [{ title: "Positioning extremes",
|
|
90
|
+
return { sections: [{ title: positions === "combined" ? `Positioning extremes · ${cotPositionsText(positions)}` : "Positioning extremes",
|
|
86
91
|
columns: [{ key: "marketName", header: "Market" }, { key: "contractCode", header: "Code" }, ...POSITION_COLUMNS,
|
|
87
92
|
{ key: "openInterest", header: "Open interest", align: "right", format: count }, { key: "reportDate", header: "As of" }],
|
|
88
93
|
rows: rows.map((row) => ({ contractCode: row.contractCode, marketName: cotMarketName(row.marketName), reportDate: row.reportDate, openInterest: row.openInterest, ...cotPositionRow(row.position) })) }],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CotClass, CotContractPayload, CotFamily, CotHistoryPoint } from "../../../api-client/cot";
|
|
1
|
+
import type { CotClass, CotContractPayload, CotFamily, CotHistoryPoint, CotPositions } from "../../../api-client/cot";
|
|
2
2
|
import { staticSeries } from "../../../components/chart/static/series";
|
|
3
3
|
import type { ResolvedSeries } from "../../../time-series/types";
|
|
4
4
|
import type { PricePoint } from "../../../types/financials";
|
|
@@ -10,7 +10,20 @@ import { bestNameMatch, FUTURES_CONTRACTS, futuresBoardContract, futuresBoardCon
|
|
|
10
10
|
export const COT_CLASSES: Record<CotFamily, Array<{ value: CotClass; label: string }>> = {
|
|
11
11
|
legacy: [{ value: "noncommercial", label: "Noncommercial" }, { value: "commercial", label: "Commercial" }, { value: "nonreportable", label: "Nonreportable" }],
|
|
12
12
|
disaggregated: [{ value: "managed-money", label: "Managed Money" }, { value: "producer", label: "Producer/Merchant" }, { value: "swap", label: "Swap Dealers" }, { value: "other-reportable", label: "Other Reportables" }, { value: "nonreportable", label: "Nonreportable" }],
|
|
13
|
+
tff: [{ value: "leveraged-funds", label: "Leveraged Funds" }, { value: "asset-manager", label: "Asset Manager/Institutional" }, { value: "dealer", label: "Dealer/Intermediary" }, { value: "other-reportable", label: "Other Reportables" }, { value: "nonreportable", label: "Nonreportable" }],
|
|
13
14
|
};
|
|
15
|
+
/** The three CFTC reports, as the pane's tabs and `--report` name them. */
|
|
16
|
+
export const COT_FAMILIES: ReadonlyArray<{ value: CotFamily; label: string }> = [
|
|
17
|
+
{ value: "legacy", label: "Legacy" }, { value: "disaggregated", label: "Disaggregated" }, { value: "tff", label: "Financial (TFF)" },
|
|
18
|
+
];
|
|
19
|
+
export function cotFamily(value: unknown): CotFamily { return value === "disaggregated" || value === "tff" ? value : "legacy"; }
|
|
20
|
+
/** Futures alone, as before, or futures and options combined. */
|
|
21
|
+
export const COT_POSITIONS: ReadonlyArray<{ value: CotPositions; label: string }> = [
|
|
22
|
+
{ value: "futures-only", label: "Futures only" }, { value: "combined", label: "Futures + options" },
|
|
23
|
+
];
|
|
24
|
+
export function cotPositions(value: unknown): CotPositions { return value === "combined" ? "combined" : "futures-only"; }
|
|
25
|
+
/** How a status line names the positions counted: "futures only" or "futures + options". */
|
|
26
|
+
export const cotPositionsText = (positions: CotPositions) => positions === "combined" ? "futures + options" : "futures only";
|
|
14
27
|
const ROOTS: Record<string, { code: string; exchange: string; priceSymbol: string | null; name: string; crypto?: true }> = {
|
|
15
28
|
ZN: { code: "043602", exchange: "CBT", priceSymbol: "ZN=F", name: "10-Year T-Note" },
|
|
16
29
|
ZQ: { code: "045601", exchange: "CBT", priceSymbol: "ZQ=F", name: "30-Day Fed Funds" },
|
|
@@ -6,16 +6,15 @@ import { useThemeColors } from "../../../theme/theme-context";
|
|
|
6
6
|
import type { PaneProps } from "../../../types/plugin";
|
|
7
7
|
import type { ResolvedSeries } from "../../../time-series/types";
|
|
8
8
|
import type { CompositeAxisDomain } from "../../../components/chart/composite/types";
|
|
9
|
-
import type { CotBoardRow, CotClass, CotClassSummary, CotFamily } from "../../../api-client/cot";
|
|
9
|
+
import type { CotBoardRow, CotClass, CotClassSummary, CotFamily, CotPositions } from "../../../api-client/cot";
|
|
10
10
|
import { isAccessDenied } from "../../../api-client/errors";
|
|
11
11
|
import { Box } from "../../../ui";
|
|
12
12
|
import { useAutoRefresh } from "../../../react/auto-refresh";
|
|
13
13
|
import { compareSortValues, nextHeaderSort, type SortPreference } from "../../../utils/sort-values";
|
|
14
14
|
import { useResearchCloudSession } from "../shared/research-cloud-session";
|
|
15
15
|
import { cachedCotBoard, loadCotBoard, loadCotDetail } from "./client";
|
|
16
|
-
import { COT_CLASSES, COT_MAJOR_CODES, COT_SCOPES, cotChartSeries, cotClass, cotContractUnitText, cotInteger, cotLegendValue, cotMarketName, cotScope, type CotScope } from "./model";
|
|
16
|
+
import { COT_CLASSES, COT_FAMILIES, COT_MAJOR_CODES, COT_POSITIONS, COT_SCOPES, cotChartSeries, cotClass, cotContractUnitText, cotFamily, cotInteger, cotLegendValue, cotMarketName, cotPositions, cotPositionsText, cotScope, type CotScope } from "./model";
|
|
17
17
|
|
|
18
|
-
const FAMILIES = [{ value: "legacy", label: "Legacy" }, { value: "disaggregated", label: "Disaggregated" }];
|
|
19
18
|
const BOARD_COLUMNS: DataTableColumn[] = [
|
|
20
19
|
{ id: "name", label: "MARKET", width: 30, align: "left", flexGrow: 1 },
|
|
21
20
|
{ id: "code", label: "CODE", width: 7, align: "left" },
|
|
@@ -44,8 +43,9 @@ const cotDetailTitle = (name: string | undefined, code: string | null) => name ?
|
|
|
44
43
|
|
|
45
44
|
export function CotPane(props: PaneProps) {
|
|
46
45
|
const pane = usePaneInstance();
|
|
47
|
-
const [
|
|
48
|
-
const
|
|
46
|
+
const [storedFamily, setFamily] = usePluginPaneState<CotFamily>("report", cotFamily(pane?.settings?.report));
|
|
47
|
+
const family = cotFamily(storedFamily);
|
|
48
|
+
const { strip: tabStrip, rows: tabRows } = usePaneTabs({ tabs: [...COT_FAMILIES], activeValue: family, onSelect: (value) => setFamily(value as CotFamily), focused: props.focused, dense: true });
|
|
49
49
|
return <Box width={props.width} height={props.height} flexDirection="column">
|
|
50
50
|
{tabStrip}
|
|
51
51
|
<CotBoard key={family} {...props} height={Math.max(3, props.height - tabRows)} family={family} initialCode={pane?.params?.code ?? null} />
|
|
@@ -63,14 +63,18 @@ function CotBoard({ width, height, focused, family, initialCode }: PaneProps & {
|
|
|
63
63
|
const open = storedOpen || null;
|
|
64
64
|
const [storedScope, setScope] = usePluginPaneState<CotScope>("scope", "major");
|
|
65
65
|
const scope = cotScope(storedScope);
|
|
66
|
+
// Futures only, as the pane always read, unless futures and options combined is picked.
|
|
67
|
+
const [storedPositions, setPositions] = usePluginPaneState<CotPositions>("positions", "futures-only");
|
|
68
|
+
const positions = cotPositions(storedPositions);
|
|
66
69
|
const [query, setQuery] = useState("");
|
|
67
70
|
const { active: searching, focus: focusSearch, searchProps } = useQueryBarSearch();
|
|
68
71
|
const [sort, setSort] = useState<SortPreference<string>>({ columnId: null, direction: "desc" });
|
|
69
72
|
const control = useRef<SelectControl>(null);
|
|
70
73
|
const scopeControl = useRef<SelectControl>(null);
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
+
const positionsControl = useRef<SelectControl>(null);
|
|
75
|
+
const loader = useCallback((force: boolean) => loadCotBoard(family, traderClass, force, positions), [family, traderClass, positions, session.requestKey]);
|
|
76
|
+
const resource = useAsyncResource(loader, { initialData: () => cachedCotBoard(family, traderClass, positions), clearOnError: isAccessDenied });
|
|
77
|
+
const data = resource.data?.traderClass === traderClass && resource.data.scope === positions ? resource.data : null;
|
|
74
78
|
const rows = useMemo(() => {
|
|
75
79
|
// A typed query searches every market; the scope only shapes the unsearched board.
|
|
76
80
|
const result = (data?.rows ?? []).filter((row) => query ? `${row.marketName} ${row.contractCode}`.toLowerCase().includes(query.toLowerCase())
|
|
@@ -90,15 +94,17 @@ function CotBoard({ width, height, focused, family, initialCode }: PaneProps & {
|
|
|
90
94
|
if (!boardShown) return;
|
|
91
95
|
if (event.name === "c") { event.preventDefault(); control.current?.open(); }
|
|
92
96
|
if (event.name === "s") { event.preventDefault(); scopeControl.current?.open(); }
|
|
97
|
+
if (event.name === "p") { event.preventDefault(); positionsControl.current?.open(); }
|
|
93
98
|
});
|
|
94
99
|
usePaneFooter("cot:actions", () => ({ hints: boardShown ? [
|
|
95
100
|
{ id: "class", key: "c", label: "class", onPress: () => control.current?.open() },
|
|
96
101
|
{ id: "scope", key: "s", label: "cope", onPress: () => scopeControl.current?.open() },
|
|
102
|
+
{ id: "positions", key: "p", label: "ositions", onPress: () => positionsControl.current?.open() },
|
|
97
103
|
{ id: "search", key: "/", label: "search", onPress: focusSearch },
|
|
98
104
|
] : [] }), [boardShown, focusSearch]);
|
|
99
105
|
usePaneNoticeFooter({ registrationId: "cot:board-notices", focused, enabled: !open, notices: data?.gaps ?? [] });
|
|
100
106
|
usePaneStatusFooter({ registrationId: "cot:board", enabled: !open, loading: resource.loading, error: resource.error,
|
|
101
|
-
info: data ? [{ id: "as-of", parts: [{ text: `CFTC
|
|
107
|
+
info: data ? [{ id: "as-of", parts: [{ text: `CFTC ${cotPositionsText(positions)} · ${data.asOf ?? "--"} · contracts`, tone: "muted" }] }] : [] });
|
|
102
108
|
// A class change reloads under the query bar, which stays.
|
|
103
109
|
return <DataTableStackView<CotBoardRow> columns={BOARD_COLUMNS} items={rows} focused={focused && !searching}
|
|
104
110
|
status={data ? undefined : { loading: resource.loading, error: resource.error, empty: !resource.loading && !resource.error, subject: "COT history" }}
|
|
@@ -118,11 +124,12 @@ function CotBoard({ width, height, focused, family, initialCode }: PaneProps & {
|
|
|
118
124
|
filters={[
|
|
119
125
|
{ id: "class", label: "Class", value: traderClass, options: COT_CLASSES[family], onChange: setClass, controlRef: control },
|
|
120
126
|
{ id: "scope", label: "Scope", value: scope, defaultValue: "major", options: [...COT_SCOPES], onChange: setScope, controlRef: scopeControl },
|
|
127
|
+
{ id: "positions", label: "Positions", value: positions, defaultValue: "futures-only", options: [...COT_POSITIONS], onChange: setPositions, controlRef: positionsControl },
|
|
121
128
|
]} />}
|
|
122
129
|
emptyStateTitle={data ? query ? "No matching COT markets." : "No major markets in this report; switch the scope to all markets." : ""} detailOpen={!!open} onBack={() => setOpen("")}
|
|
123
130
|
detailTitle={cotDetailTitle(data?.rows.find((row) => row.contractCode === open)?.marketName, open)}
|
|
124
|
-
detailContent={open ? <CotDetail key={`${family}:${open}`} width={width} height={Math.max(3, height - 1)} focused={focused}
|
|
125
|
-
code={open} family={family} traderClass={traderClass} onClassChange={setClass} /> : null} />;
|
|
131
|
+
detailContent={open ? <CotDetail key={`${family}:${positions}:${open}`} width={width} height={Math.max(3, height - 1)} focused={focused}
|
|
132
|
+
code={open} family={family} positions={positions} traderClass={traderClass} onClassChange={setClass} /> : null} />;
|
|
126
133
|
}
|
|
127
134
|
|
|
128
135
|
/**
|
|
@@ -147,10 +154,10 @@ function CotChart({ series, width, height }: { series: ResolvedSeries[]; width:
|
|
|
147
154
|
remoteKind="cot-history" />;
|
|
148
155
|
}
|
|
149
156
|
|
|
150
|
-
function CotDetail({ width, height, focused, code, family, traderClass, onClassChange }: Pick<PaneProps, "width" | "height" | "focused"> & { code: string; family: CotFamily; traderClass: CotClass; onClassChange: (value: CotClass) => void }) {
|
|
157
|
+
function CotDetail({ width, height, focused, code, family, positions, traderClass, onClassChange }: Pick<PaneProps, "width" | "height" | "focused"> & { code: string; family: CotFamily; positions: CotPositions; traderClass: CotClass; onClassChange: (value: CotClass) => void }) {
|
|
151
158
|
const colors = useThemeColors();
|
|
152
159
|
const session = useResearchCloudSession();
|
|
153
|
-
const loader = useCallback(() => loadCotDetail(code, family), [code, family, session.requestKey]);
|
|
160
|
+
const loader = useCallback(() => loadCotDetail(code, family, undefined, positions), [code, family, positions, session.requestKey]);
|
|
154
161
|
const resource = useAsyncResource(loader, { clearOnError: isAccessDenied });
|
|
155
162
|
const data = resource.data;
|
|
156
163
|
const payload = data?.payload;
|
|
@@ -178,7 +185,8 @@ function CotDetail({ width, height, focused, code, family, traderClass, onClassC
|
|
|
178
185
|
usePaneNoticeFooter({ registrationId: "cot:detail-notices", focused, notices: [...(payload?.gaps ?? []), ...(data?.priceWarning ? [data.priceWarning] : []), ...partialRanks] });
|
|
179
186
|
usePaneStatusFooter({ registrationId: "cot:detail", loading: resource.loading, error: resource.error,
|
|
180
187
|
// The unit the net is counted in, as `fn COT` titles it: what one contract is, where the report says.
|
|
181
|
-
|
|
188
|
+
// Futures and options combined says so; futures only reads as it always has.
|
|
189
|
+
info: payload ? [{ id: "as-of", parts: [{ text: `CFTC ${positions === "combined" ? `${cotPositionsText(positions)} · ` : ""}${payload.asOf ?? "--"} · ${cotContractUnitText(payload.contract?.contractUnits) ?? "contracts"}${data?.priceAsOf ? ` · ${data.priceSymbol} ${data.priceAsOf}` : ""}`, tone: "muted" }] }] : [] });
|
|
182
190
|
return <PaneStatusBody loading={resource.loading && !data} error={!data ? resource.error : null} empty={!!payload && !payload.contract} subject="COT contract">
|
|
183
191
|
{payload && data ? <DataTableView columns={POSITION_COLUMNS} items={payload.positions} focused={focused} rootWidth={width} rootHeight={height}
|
|
184
192
|
rootBefore={<ChartTableHeader width={width} height={height} tableRows={payload.positions.length} chart={chart}
|
|
@@ -5,7 +5,9 @@ import type {
|
|
|
5
5
|
SovrBoardPayload,
|
|
6
6
|
} from "../../../api-client/credit-boards";
|
|
7
7
|
import { createPluginCache } from "../../../data/plugin-cache";
|
|
8
|
+
import { createTtlCache } from "../../../data/ttl-cache";
|
|
8
9
|
import type { PricePoint } from "../../../types/financials";
|
|
10
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
9
11
|
import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
10
12
|
import { currencyMoveFromHistory, currencyPair } from "./model";
|
|
11
13
|
|
|
@@ -84,18 +86,12 @@ export type PriceHistoryLoader = (symbol: string) => Promise<PricePoint[]>;
|
|
|
84
86
|
const FX_HISTORY_TTL_MS = 30 * 60_000;
|
|
85
87
|
/** Twenty-odd currencies at once; a few requests at a time keep the feed from refusing them. */
|
|
86
88
|
const FX_HISTORY_CONCURRENCY = 4;
|
|
87
|
-
const fxMoves =
|
|
89
|
+
const fxMoves = createTtlCache<number | null>({ ttlMs: FX_HISTORY_TTL_MS });
|
|
88
90
|
|
|
91
|
+
/** A load that fails reads as unknown for everyone waiting on it and is asked again next time, never remembered as unknown. */
|
|
89
92
|
function currencyMove(symbol: string, dollarsPerUnit: boolean, loadHistory: PriceHistoryLoader): Promise<number | null> {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const move = loadHistory(symbol).then((history) => currencyMoveFromHistory(history, dollarsPerUnit));
|
|
93
|
-
fxMoves.set(symbol, { at: Date.now(), move });
|
|
94
|
-
// A failed load is retried next time rather than remembered as unknown.
|
|
95
|
-
return move.catch(() => {
|
|
96
|
-
fxMoves.delete(symbol);
|
|
97
|
-
return null;
|
|
98
|
-
});
|
|
93
|
+
return fxMoves.load(symbol, async () => currencyMoveFromHistory(await loadHistory(symbol), dollarsPerUnit))
|
|
94
|
+
.catch(logFailure("credit-boards", "Read a currency's price history failed", null));
|
|
99
95
|
}
|
|
100
96
|
|
|
101
97
|
/**
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type CreditConditionsLoadResult,
|
|
8
8
|
type CreditSeriesLoader,
|
|
9
9
|
} from "./client";
|
|
10
|
-
import { creditRegion, creditSeriesIn } from "./model";
|
|
10
|
+
import { creditRegion, creditSeriesIn, type CreditConditionRow } from "./model";
|
|
11
11
|
|
|
12
12
|
const COLUMNS = [
|
|
13
13
|
{ key: "label", header: "Index" },
|
|
@@ -22,6 +22,11 @@ const COLUMNS = [
|
|
|
22
22
|
{ key: "date", header: "As of" },
|
|
23
23
|
];
|
|
24
24
|
|
|
25
|
+
/** What the publisher caps the indexes' history at, said once for the report. */
|
|
26
|
+
function historyLimits(rows: readonly CreditConditionRow[]): string[] {
|
|
27
|
+
return [...new Set(rows.flatMap((row) => row.historyLimit ? [row.historyLimit] : []))];
|
|
28
|
+
}
|
|
29
|
+
|
|
25
30
|
interface CreditConditionsHeadlessDependencies {
|
|
26
31
|
load(args: HeadlessPaneLoadArgs, loader: CreditSeriesLoader): Promise<CreditConditionsLoadResult>;
|
|
27
32
|
}
|
|
@@ -57,7 +62,8 @@ function createCreditConditionsHeadless(
|
|
|
57
62
|
);
|
|
58
63
|
return {
|
|
59
64
|
// The year of history feeds the pane's chart and rank, not the report rows.
|
|
60
|
-
rows: result.rows.map(({ history: _history, ...row }) => row),
|
|
65
|
+
rows: result.rows.map(({ history: _history, historyLimit: _historyLimit, ...row }) => row),
|
|
66
|
+
notes: historyLimits(result.rows),
|
|
61
67
|
errors: result.errors,
|
|
62
68
|
metadata: { stale: result.stale },
|
|
63
69
|
};
|
|
@@ -110,10 +110,14 @@ export function CreditConditionsPane({ paneId, focused, width, height }: PanePro
|
|
|
110
110
|
const partial = rows.length > 0 && rows.length < series.length;
|
|
111
111
|
// Each row carries its own date in the AS OF column; a spread between
|
|
112
112
|
// indexes from different sessions is a limitation behind the warning.
|
|
113
|
+
const historyLimits = useMemo(() => [...new Set(rows.flatMap((row) => row.historyLimit ? [row.historyLimit] : []))], [rows]);
|
|
113
114
|
usePaneNoticeFooter({
|
|
114
115
|
registrationId: "credit-conditions:notices",
|
|
115
116
|
focused,
|
|
116
|
-
notices:
|
|
117
|
+
notices: [
|
|
118
|
+
...mixedDates ? ["The indexes carry different observation dates, so the spreads are not one session."] : [],
|
|
119
|
+
...historyLimits,
|
|
120
|
+
],
|
|
117
121
|
});
|
|
118
122
|
const footerInfo = useMemo<PaneFooterSegment[]>(() => [
|
|
119
123
|
...(rows.length > 0 ? [DELAYED_SEGMENT] : []),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CloudFredSeriesPayload } from "../../../api-client";
|
|
2
2
|
import { historyStatistics } from "../../../components/chart/curve/model";
|
|
3
|
+
import { fredHistoryLimitNotice } from "../../../time-series/economic";
|
|
3
4
|
import { ICE_SPREAD_SERIES, type IceCreditSeries } from "../../../time-series/ice-credit";
|
|
4
5
|
|
|
5
6
|
export type CreditSeriesDefinition = Pick<IceCreditSeries, "seriesId" | "label" | "region">;
|
|
@@ -30,6 +31,8 @@ export interface CreditConditionRow {
|
|
|
30
31
|
dailyChangeBp: number | null;
|
|
31
32
|
date: string;
|
|
32
33
|
stale: boolean;
|
|
34
|
+
/** The publisher's cap on how far back the series goes, when FRED states one. */
|
|
35
|
+
historyLimit: string | null;
|
|
33
36
|
/** Observations in the year to `date`, in basis points, oldest first. */
|
|
34
37
|
history: Array<{ date: string; valueBp: number }>;
|
|
35
38
|
/** Midrank of the latest spread within that year. */
|
|
@@ -85,6 +88,7 @@ export function normalizeCreditSeries(
|
|
|
85
88
|
dailyChangeBp: previous ? roundTenth((latest.value - previous.value) * 100) : null,
|
|
86
89
|
date: latest.date,
|
|
87
90
|
stale,
|
|
91
|
+
historyLimit: fredHistoryLimitNotice(definition.seriesId, info),
|
|
88
92
|
history,
|
|
89
93
|
// One observation ranks nothing.
|
|
90
94
|
percentile1Y: year.count >= 2 ? year.percentile : null,
|