gloomberb 0.18.4 → 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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +169 -5
- package/src/account/auth-model.ts +7 -3
- package/src/api-client/auth.ts +9 -4
- package/src/api-client/catalysts.ts +2 -1
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +31 -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 +171 -11
- package/src/api-client/live-scan.ts +91 -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 +13 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/request.ts +222 -41
- package/src/api-client/research-activity.ts +6 -12
- package/src/api-client/types.ts +74 -0
- package/src/app/keybindings/index.ts +3 -0
- package/src/app/keybindings/labels.ts +18 -1
- package/src/app/keybindings/resolve.ts +14 -0
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +42 -27
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +1 -1
- 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/fred-series.ts +5 -0
- package/src/cli/commands/keybindings.ts +23 -0
- package/src/cli/commands/market.ts +138 -46
- package/src/cli/commands/options-chain.ts +107 -10
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +176 -39
- package/src/cli/commands/search.ts +39 -3
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +57 -35
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +10 -2
- package/src/cli/export-source.ts +29 -1
- package/src/cli/glossary.ts +73 -1
- package/src/cli/history-rows.ts +87 -16
- package/src/cli/index.ts +2 -0
- package/src/cli/known-gaps.ts +5 -2
- package/src/cli/listing-arg.ts +195 -56
- 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 +111 -16
- package/src/cli/pane-functions/function-help.ts +23 -13
- package/src/cli/pane-functions/headless.ts +56 -12
- package/src/cli/pane-functions/index.ts +58 -16
- package/src/cli/pane-functions/market-session.ts +81 -9
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/report.ts +5 -1
- package/src/cli/pane-functions/resolver.ts +35 -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 +30 -13
- package/src/cli/report-tables.ts +18 -7
- package/src/cli/restore-plugins.ts +13 -1
- package/src/cli/result.ts +57 -5
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +26 -4
- package/src/components/chart/composite/format.ts +27 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/levels.ts +2 -0
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +61 -5
- package/src/components/chart/composite/range-statistics.ts +62 -0
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +46 -7
- package/src/components/chart/composite/series-axis-tags.tsx +16 -9
- 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/types.ts +4 -0
- 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/panel/prompt-binding.ts +0 -8
- package/src/components/command-bar/workflow/alert.ts +1 -1
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/header.tsx +26 -40
- package/src/components/layout/pane/footer/index.tsx +1 -0
- package/src/components/layout/pane/footer/model.ts +27 -0
- package/src/components/onboarding/onboarding-frame.tsx +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
- 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/display.tsx +5 -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/core/state/app/layout.ts +2 -0
- package/src/core/state/app/state.ts +9 -0
- package/src/core/state/app/types.ts +3 -0
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/saved-copies.ts +55 -16
- 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 +427 -216
- package/src/i18n/es.ts +19 -3
- package/src/i18n/ja.ts +19 -3
- package/src/i18n/ko.ts +19 -3
- package/src/i18n/zh-cn.ts +19 -3
- package/src/i18n/zh-tw.ts +19 -3
- package/src/market-data/coordinator/financials.ts +10 -2
- package/src/market-data/coordinator/index.ts +3 -1
- package/src/market-data/coordinator/quotes.ts +4 -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 +48 -7
- package/src/market-data/market/dated-previous-close.ts +203 -0
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/market/futures-week.ts +68 -0
- package/src/market-data/market/status.ts +16 -4
- package/src/market-data/options-alternatives.ts +141 -4
- package/src/market-data/quotes/contributions.ts +20 -2
- package/src/market-data/quotes/resolution.ts +3 -0
- package/src/market-data/selectors.ts +3 -0
- 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/notifications/notification-log.ts +6 -4
- package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +380 -82
- package/src/plugins/builtin/alerts/command.ts +31 -0
- package/src/plugins/builtin/alerts/constants.ts +1 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- package/src/plugins/builtin/alerts/fired-log.ts +113 -8
- package/src/plugins/builtin/alerts/format.ts +80 -4
- package/src/plugins/builtin/alerts/headless.ts +30 -7
- package/src/plugins/builtin/alerts/history-rows.ts +7 -3
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +135 -22
- package/src/plugins/builtin/alerts/live.ts +3 -3
- package/src/plugins/builtin/alerts/metric-command.ts +134 -0
- package/src/plugins/builtin/alerts/metric-data.ts +111 -0
- package/src/plugins/builtin/alerts/metric-model.ts +324 -0
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
- package/src/plugins/builtin/alerts/pane.tsx +90 -39
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +37 -10
- package/src/plugins/builtin/alerts/types.ts +7 -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/backtest/headless.ts +10 -5
- package/src/plugins/builtin/backtest/pane.tsx +9 -4
- package/src/plugins/builtin/backtest/presets.ts +29 -3
- package/src/plugins/builtin/broker-manager/index.tsx +10 -5
- package/src/plugins/builtin/broker-manager/model.ts +6 -1
- package/src/plugins/builtin/broker-trading/controller.ts +22 -12
- package/src/plugins/builtin/broker-trading/index.tsx +13 -1
- package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
- package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
- 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 +7 -3
- package/src/plugins/builtin/catalysts/model.ts +2 -1
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
- package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
- package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
- package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
- package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
- package/src/plugins/builtin/chart-composer/formula.ts +71 -18
- package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
- package/src/plugins/builtin/chart-composer/headless.ts +19 -3
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
- package/src/plugins/builtin/chart-composer/presets.ts +50 -15
- package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
- package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
- package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
- package/src/plugins/builtin/chat/content/composer.tsx +13 -6
- package/src/plugins/builtin/chat/content/footer.tsx +6 -3
- package/src/plugins/builtin/chat/content/index.tsx +50 -21
- package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
- package/src/plugins/builtin/chat/index.tsx +23 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
- package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
- package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
- package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -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/cloud/plugin.tsx +1 -1
- 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/calendar-model.ts +14 -9
- package/src/plugins/builtin/econ/figures.ts +72 -0
- package/src/plugins/builtin/econ/index.tsx +33 -3
- package/src/plugins/builtin/econ/types.ts +4 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
- package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
- package/src/plugins/builtin/econ-statistics/view.ts +3 -0
- 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 +70 -15
- package/src/plugins/builtin/exposure/charts.tsx +2 -2
- package/src/plugins/builtin/exposure/model.ts +13 -1
- package/src/plugins/builtin/exposure/pane.tsx +4 -4
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/futures/model.ts +2 -1
- package/src/plugins/builtin/futures/table.tsx +2 -2
- 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 +11 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
- package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
- package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -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/keybindings-editor.tsx +18 -14
- 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 +36 -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/pane.tsx +7 -5
- 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/footer.ts +2 -2
- 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 +82 -0
- package/src/plugins/builtin/members/client.ts +19 -4
- package/src/plugins/builtin/members/headless.ts +20 -7
- package/src/plugins/builtin/members/index.tsx +9 -9
- package/src/plugins/builtin/members/model.ts +23 -4
- package/src/plugins/builtin/members/pane.tsx +25 -15
- 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 +54 -6
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/model-spot.ts +5 -2
- package/src/plugins/builtin/options/strike-window.ts +2 -1
- package/src/plugins/builtin/options/table.ts +6 -9
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +42 -12
- package/src/plugins/builtin/options-positioning/client.ts +30 -0
- package/src/plugins/builtin/options-positioning/headless.ts +74 -21
- package/src/plugins/builtin/options-positioning/index.tsx +4 -4
- package/src/plugins/builtin/options-positioning/model.ts +122 -14
- package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
- 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/freshness.ts +12 -10
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/live-rows.ts +10 -3
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +32 -31
- 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 +12 -7
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- 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/summary/index.tsx +11 -8
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/rate-path/pane.tsx +2 -2
- 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 +11 -9
- 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/feed.ts +2 -2
- 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 +40 -6
- package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
- package/src/plugins/builtin/scanner/live-feed.ts +168 -0
- package/src/plugins/builtin/scanner/live-headless.ts +138 -0
- package/src/plugins/builtin/scanner/live-model.ts +362 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
- package/src/plugins/builtin/scanner/live-track.ts +186 -0
- 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 +3 -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/macro-releases.ts +65 -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/scanner-capture.ts +3 -0
- 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 +10 -3
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/headless.ts +6 -4
- package/src/plugins/builtin/themes/model.ts +17 -8
- package/src/plugins/builtin/themes/pane.tsx +7 -7
- 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-footer.ts +6 -1
- 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/charts.tsx +9 -4
- package/src/plugins/builtin/vol-surface/client.ts +9 -17
- package/src/plugins/builtin/vol-surface/headless.ts +63 -14
- package/src/plugins/builtin/vol-surface/index.tsx +2 -2
- package/src/plugins/builtin/vol-surface/model.ts +22 -7
- package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
- package/src/plugins/builtin/vol-surface/tables.ts +31 -3
- package/src/plugins/builtin/volatility/dvol.ts +46 -0
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +4 -4
- package/src/plugins/builtin/volatility/series.ts +26 -0
- package/src/plugins/builtin/world-indices/footer.ts +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-browser.ts +13 -8
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +5 -5
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
- package/src/remote/resources.ts +2 -1
- package/src/renderers/browser/app-services.ts +1 -7
- package/src/renderers/browser/config-host.ts +2 -55
- package/src/renderers/browser/main.tsx +7 -57
- 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 +222 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
- 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/relaunch.ts +74 -25
- package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
- package/src/renderers/electrobun/bun/index.ts +25 -1
- package/src/renderers/electrobun/shared/app-name.ts +30 -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/cli-pane-shot-frame.tsx +3 -3
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -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/renderers/opentui/toast-host.tsx +71 -43
- 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/gloomberb-cloud/normalizers.ts +2 -0
- package/src/sources/provider-router/batches.ts +10 -2
- package/src/sources/provider-router/financial-routes.ts +3 -3
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/history.ts +28 -7
- package/src/sources/provider-router/index.ts +149 -10
- package/src/sources/provider-router/search.ts +13 -2
- package/src/state/app/context/index.tsx +3 -0
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/auth-transition.ts +19 -9
- package/src/sync/baseline.ts +10 -4
- package/src/sync/controller.ts +70 -19
- package/src/sync/core-contributors.ts +90 -31
- package/src/sync/react.ts +4 -1
- package/src/sync/types.ts +3 -0
- 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/telemetry/usage-counts.ts +0 -18
- package/src/test-support/preload.ts +14 -0
- package/src/theme/colors.ts +8 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name.ts +103 -3
- package/src/tickers/listing-venues.ts +38 -3
- package/src/tickers/search/bare-symbol.ts +36 -0
- package/src/tickers/search/index.ts +36 -7
- package/src/tickers/search/ranking.ts +10 -1
- package/src/tickers/search/types.ts +5 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/base-series.ts +13 -5
- package/src/time-series/daily-valuation.ts +62 -0
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +2 -4
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/forward-valuation.ts +32 -0
- package/src/time-series/fundamentals.ts +198 -7
- package/src/time-series/hooks.ts +1 -0
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/price-units.ts +72 -2
- package/src/time-series/resolve-context.ts +7 -1
- package/src/time-series/resolve.ts +116 -15
- package/src/time-series/series-presentation.ts +40 -6
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +5 -3
- package/src/time-series/studies.ts +278 -74
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +3 -3
- package/src/time-series/types.ts +27 -1
- package/src/time-series/valuation-releases.ts +39 -0
- package/src/types/data-provider.ts +9 -1
- package/src/types/desktop-window.ts +2 -0
- package/src/types/financials.ts +73 -2
- package/src/types/headless.ts +61 -5
- package/src/types/instrument.ts +3 -0
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +10 -1
- package/src/ui/host.tsx +3 -0
- package/src/updater/command-names.ts +12 -7
- 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/financial-statements.ts +6 -1
- 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/network-failure.ts +9 -6
- package/src/utils/option-expiry.ts +48 -1
- package/src/utils/options.ts +10 -0
- package/src/utils/plugin-proxy-hosts.json +1 -0
- package/src/utils/price-history.ts +12 -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
- package/src/renderers/browser/first-screen.ts +0 -193
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useEffect, useMemo } from "react";
|
|
2
|
+
import { getSharedMarketDataCoordinator } from "../../../../market-data/coordinator";
|
|
3
|
+
import { formatMarketPrice, quoteFormatOptions, withCurrencyMinorDigits } from "../../../../market-data/market/format";
|
|
4
|
+
import { buildQuoteKey, instrumentFromQuoteTarget, resolveEntryData } from "../../../../market-data/selectors";
|
|
5
|
+
import type { PortfolioFuturesTerms } from "../../../../portfolio/futures-positions";
|
|
6
|
+
import { useLiveQuoteEntries } from "../../../../state/hooks/quote-streaming";
|
|
7
|
+
import type { QuoteSubscriptionTarget } from "../../../../types/data-provider";
|
|
8
|
+
import { logFailure } from "../../../../utils/log-failure";
|
|
9
|
+
|
|
10
|
+
/** Front references share one snapshot and stream per contract, independent of each row's own price. */
|
|
11
|
+
export function useFuturesFrontNotes(
|
|
12
|
+
terms: ReadonlyMap<string, PortfolioFuturesTerms>,
|
|
13
|
+
{ enabled, liveStreaming }: { enabled: boolean; liveStreaming: boolean },
|
|
14
|
+
): ReadonlyMap<string, string> {
|
|
15
|
+
const symbolsKey = useMemo(() => enabled
|
|
16
|
+
? [...new Set([...terms.values()].flatMap((value) => value.front?.contractSymbol ?? []))].sort().join("\u001f") : "", [enabled, terms]);
|
|
17
|
+
const targets = useMemo<QuoteSubscriptionTarget[]>(() => symbolsKey ? symbolsKey.split("\u001f").map((symbol) => ({
|
|
18
|
+
symbol, exchange: "", route: "provider", surface: "watchlist", visible: true,
|
|
19
|
+
})) : [], [symbolsKey]);
|
|
20
|
+
const instruments = useMemo(() => targets.map(instrumentFromQuoteTarget), [targets]);
|
|
21
|
+
const coordinator = getSharedMarketDataCoordinator();
|
|
22
|
+
const { entries } = useLiveQuoteEntries(targets, { liveStreaming });
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
// Polling primes its own batch immediately; a stream needs one snapshot while connecting.
|
|
25
|
+
if (!liveStreaming || !coordinator || !instruments.length) return;
|
|
26
|
+
void coordinator.loadQuotesBatch(instruments).catch(logFailure("portfolio-futures", "Read front contract quotes failed"));
|
|
27
|
+
}, [coordinator, instruments, liveStreaming]);
|
|
28
|
+
return useMemo(() => {
|
|
29
|
+
const notes = new Map<string, string>();
|
|
30
|
+
if (!enabled) return notes;
|
|
31
|
+
for (const [symbol, value] of terms) {
|
|
32
|
+
if (!value.front) continue;
|
|
33
|
+
const quote = resolveEntryData(entries.get(buildQuoteKey({ symbol: value.front.contractSymbol, exchange: "" })));
|
|
34
|
+
if (!quote || !Number.isFinite(quote.price)) continue;
|
|
35
|
+
const options = withCurrencyMinorDigits(quoteFormatOptions(quote, "FUTURE"), quote.currency);
|
|
36
|
+
notes.set(symbol, `front ${value.front.contract} ${formatMarketPrice(quote.price, options)}`);
|
|
37
|
+
}
|
|
38
|
+
return notes;
|
|
39
|
+
}, [enabled, entries, terms]);
|
|
40
|
+
}
|
|
@@ -104,6 +104,7 @@ import { hasUnknownOptionMultiplier } from "../../../../portfolio/position-metri
|
|
|
104
104
|
import { withFuturesContractSizes } from "../../../../portfolio/futures-positions";
|
|
105
105
|
import { withPurchaseDateCostFx } from "../../../../portfolio/cost-fx";
|
|
106
106
|
import { usePortfolioFuturesTerms } from "./futures-terms";
|
|
107
|
+
import { useFuturesFrontNotes } from "./futures-front";
|
|
107
108
|
import { usePurchaseDateCostFx } from "./purchase-date-fx";
|
|
108
109
|
import {
|
|
109
110
|
allocationWeightBase,
|
|
@@ -185,7 +186,8 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
|
|
|
185
186
|
[config.baseCurrency, currentPortfolio],
|
|
186
187
|
);
|
|
187
188
|
// A future counts contracts of the size its terms give, and at the close is valued at its settlement.
|
|
188
|
-
const futuresTerms = usePortfolioFuturesTerms(collectionTickers, { enabled:
|
|
189
|
+
const futuresTerms = usePortfolioFuturesTerms(collectionTickers, { enabled: true, settlement: isPortfolioTab && valuationBasis === "regular-close" });
|
|
190
|
+
const futuresFrontNotes = useFuturesFrontNotes(futuresTerms.terms, { enabled: appActive && viewMode === "table", liveStreaming });
|
|
189
191
|
// A dated lot in another currency costs what its purchase date's FX close says in that currency, as portfolio show values it.
|
|
190
192
|
const purchaseDateFx = usePurchaseDateCostFx(collectionTickers, { enabled: isPortfolioTab, portfolioId: activeCollectionId, into: totalsCurrency });
|
|
191
193
|
const tickers = useMemo(
|
|
@@ -796,6 +798,7 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
|
|
|
796
798
|
onCursorChange={handleCursorChange}
|
|
797
799
|
financialsMap={financialsMap}
|
|
798
800
|
columnContext={columnContext}
|
|
801
|
+
futuresFrontNotes={futuresFrontNotes}
|
|
799
802
|
flashSymbols={flashSymbols}
|
|
800
803
|
sinceLastSeen={sinceLastSeen}
|
|
801
804
|
onRootKeyDown={handleTableKeyDown}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../../utils/log-failure";
|
|
1
2
|
import { useEffect, useMemo, useRef } from "react";
|
|
2
3
|
import { getSharedMarketDataCoordinator } from "../../../../market-data/coordinator";
|
|
3
4
|
import { instrumentFromTicker, quoteSubscriptionTargetFromTicker } from "../../../../market-data/request-types";
|
|
@@ -251,8 +252,9 @@ export function usePortfolioPaneStreaming({
|
|
|
251
252
|
? sharedCoordinator.loadSnapshotsBatch(normalSnapshotEntries.map((entry) => entry.instrument))
|
|
252
253
|
: Promise.resolve(),
|
|
253
254
|
]);
|
|
254
|
-
} catch {
|
|
255
|
+
} catch (error) {
|
|
255
256
|
// Best-effort warmup for visible rows only.
|
|
257
|
+
logFailure("portfolio-list.pane.streaming", "Warm visible portfolio rows failed")(error);
|
|
256
258
|
} finally {
|
|
257
259
|
for (const entry of [...quoteEntries, ...forcedSnapshotEntries, ...normalSnapshotEntries]) {
|
|
258
260
|
warmupInFlightRef.current.delete(entry.key);
|
|
@@ -304,8 +306,9 @@ export function usePortfolioPaneStreaming({
|
|
|
304
306
|
|
|
305
307
|
try {
|
|
306
308
|
await sharedCoordinator.loadQuotesBatch(quoteEntries.map((entry) => entry.instrument), { forceRefresh: true });
|
|
307
|
-
} catch {
|
|
309
|
+
} catch (error) {
|
|
308
310
|
// Best-effort watchdog for visible rows only.
|
|
311
|
+
logFailure("portfolio-list.pane.streaming", "Refresh visible portfolio quotes failed")(error);
|
|
309
312
|
} finally {
|
|
310
313
|
for (const entry of quoteEntries) {
|
|
311
314
|
warmupInFlightRef.current.delete(entry.key);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../../utils/log-failure";
|
|
1
2
|
import { useEffect, useMemo, useRef, useState, type Dispatch, type SetStateAction } from "react";
|
|
2
3
|
import type { ColumnConfig } from "../../../../types/config";
|
|
3
4
|
import type { AnalystResearchData, CorporateActionsData, TickerFinancials } from "../../../../types/financials";
|
|
@@ -171,7 +172,7 @@ function useAnalystSupplemental(
|
|
|
171
172
|
}
|
|
172
173
|
})).then((entries) => {
|
|
173
174
|
if (mountedRef.current) setState((current) => setAnalystValues(current, entries));
|
|
174
|
-
}).catch((
|
|
175
|
+
}).catch(logFailure("portfolio-list.pane.supplemental", "Apply analyst research failed"));
|
|
175
176
|
}, [enabled, mountedRef, provider, setState, state.analystResearch, targetsKey]);
|
|
176
177
|
}
|
|
177
178
|
|
|
@@ -211,7 +212,7 @@ function useCorporateSupplemental(
|
|
|
211
212
|
}
|
|
212
213
|
})).then((entries) => {
|
|
213
214
|
if (mountedRef.current) setState((current) => setCorporateValues(current, entries));
|
|
214
|
-
}).catch((
|
|
215
|
+
}).catch(logFailure("portfolio-list.pane.supplemental", "Apply corporate actions failed"));
|
|
215
216
|
}, [enabled, mountedRef, provider, setState, state.corporateActions, targetsKey]);
|
|
216
217
|
}
|
|
217
218
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../../utils/log-failure";
|
|
1
2
|
import { getSharedRegistry } from "../../../registry";
|
|
2
3
|
import {
|
|
3
4
|
AmbiguousContractError,
|
|
@@ -178,7 +179,8 @@ export async function resolveQuickAddValidation({
|
|
|
178
179
|
exchangeFromResolved(resolved),
|
|
179
180
|
quoteContextFromResolved(resolved),
|
|
180
181
|
);
|
|
181
|
-
} catch {
|
|
182
|
+
} catch (error) {
|
|
183
|
+
logFailure("portfolio-list.quick-add.resolution", "Read quick-add quote failed")(error);
|
|
182
184
|
quote = null;
|
|
183
185
|
}
|
|
184
186
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Box, Text } from "../../../../ui";
|
|
2
2
|
import { TextAttributes } from "../../../../ui";
|
|
3
|
-
import { colors, priceColor } from "../../../../theme/colors";
|
|
3
|
+
import { colors, priceColor, shownPriceColor } from "../../../../theme/colors";
|
|
4
4
|
import type { AppState } from "../../../../state/app/context";
|
|
5
|
-
import type
|
|
5
|
+
import { savedCopyFooterSegment, type PaneFooterSegment } from "../../../../components/layout/pane/footer/model";
|
|
6
|
+
import { oldestSavedCopyAt } from "../../../../data/saved-copies";
|
|
6
7
|
import type { BrokerConnectionStatus } from "../../../../types/broker";
|
|
7
8
|
import type { TickerFinancials } from "../../../../types/financials";
|
|
8
9
|
import type { Portfolio, TickerRecord } from "../../../../types/ticker";
|
|
@@ -266,7 +267,7 @@ export function buildPortfolioSummarySegments({
|
|
|
266
267
|
return totals.watchlistCount > 0
|
|
267
268
|
? [createSummarySegment("avg-day", [
|
|
268
269
|
{ text: "Avg Day", tone: "label" },
|
|
269
|
-
{ text: formatPercentRaw(totals.avgWatchlistChange), tone: "value", color:
|
|
270
|
+
{ text: formatPercentRaw(totals.avgWatchlistChange), tone: "value", color: shownPriceColor(totals.avgWatchlistChange, 2), bold: true },
|
|
270
271
|
...dayBasisPart(totals),
|
|
271
272
|
])]
|
|
272
273
|
: [];
|
|
@@ -341,7 +342,7 @@ export function buildPortfolioSummarySegments({
|
|
|
341
342
|
candidates.push(createSummarySegment("day", [
|
|
342
343
|
{ text: "Day", tone: "label" },
|
|
343
344
|
{ text: money(accountMetrics.dailyPnl, true), tone: "value", color: priceColor(accountMetrics.dailyPnl), bold: true },
|
|
344
|
-
{ text: `(${formatPercentRaw(accountMetrics.dailyPnlPct)})`, tone: "muted", color:
|
|
345
|
+
{ text: `(${formatPercentRaw(accountMetrics.dailyPnlPct)})`, tone: "muted", color: shownPriceColor(accountMetrics.dailyPnlPct, 2) },
|
|
345
346
|
// The broker's own day is from its account's close, not the quotes': it names none.
|
|
346
347
|
...(accountMetrics.brokerDay || !Number.isFinite(accountMetrics.dailyPnl) ? [] : dayBasisPart(totals)),
|
|
347
348
|
]));
|
|
@@ -351,7 +352,7 @@ export function buildPortfolioSummarySegments({
|
|
|
351
352
|
: !Number.isFinite(account?.unrealizedPnl)
|
|
352
353
|
&& totals.unrealizedPnlBasis === "mixed" ? "Mixed P&L" : "P&L", tone: "label" },
|
|
353
354
|
{ text: money(accountMetrics.unrealizedPnl, true), tone: "value", color: priceColor(accountMetrics.unrealizedPnl), bold: true },
|
|
354
|
-
{ text: `(${formatPercentRaw(accountMetrics.unrealizedPnlPct)})`, tone: "muted", color:
|
|
355
|
+
{ text: `(${formatPercentRaw(accountMetrics.unrealizedPnlPct)})`, tone: "muted", color: shownPriceColor(accountMetrics.unrealizedPnlPct, 2) },
|
|
355
356
|
]));
|
|
356
357
|
|
|
357
358
|
// A book with shorts splits its gross into the two sides, after the headline numbers.
|
|
@@ -446,11 +447,13 @@ export function buildPortfolioFooterSegments({
|
|
|
446
447
|
: [];
|
|
447
448
|
// Cached account numbers stay on screen when a live refresh fails, so the failure sits beside their date.
|
|
448
449
|
if (accountState) return [...source, ...accountStatus];
|
|
450
|
+
const quotes = sortedTickers.map((ticker) => financialsMap.get(ticker.metadata.ticker)?.quote);
|
|
451
|
+
// Offline, the quotes listed are copies saved when they last refreshed: the footer says when, in place of the quote time.
|
|
452
|
+
const savedAt = refreshingSize > 0 ? null : oldestSavedCopyAt(quotes);
|
|
453
|
+
if (savedAt != null) return [...accountStatus, savedCopyFooterSegment("refresh", savedAt)];
|
|
449
454
|
if (isPortfolioTab ? !totals.hasPositions && !accountStatusText : totals.watchlistCount === 0) return [];
|
|
450
455
|
|
|
451
|
-
const lastRefreshTimestamp = getMostRecentQuoteUpdate(
|
|
452
|
-
sortedTickers.map((ticker) => financialsMap.get(ticker.metadata.ticker)?.quote),
|
|
453
|
-
);
|
|
456
|
+
const lastRefreshTimestamp = getMostRecentQuoteUpdate(quotes);
|
|
454
457
|
const refreshText = refreshingSize > 0
|
|
455
458
|
? "Refreshing..."
|
|
456
459
|
: lastRefreshTimestamp != null
|
|
@@ -111,6 +111,7 @@ export function PortfolioTickerTable({
|
|
|
111
111
|
onCursorChange,
|
|
112
112
|
financialsMap,
|
|
113
113
|
columnContext,
|
|
114
|
+
futuresFrontNotes,
|
|
114
115
|
flashSymbols,
|
|
115
116
|
sinceLastSeen,
|
|
116
117
|
onRootKeyDown,
|
|
@@ -134,6 +135,7 @@ export function PortfolioTickerTable({
|
|
|
134
135
|
onCursorChange?: (ticker: TickerRecord, index: number) => void;
|
|
135
136
|
financialsMap: Map<string, TickerFinancials>;
|
|
136
137
|
columnContext: ColumnContext;
|
|
138
|
+
futuresFrontNotes?: ReadonlyMap<string, string>;
|
|
137
139
|
flashSymbols: Map<string, QuoteFlashDirection>;
|
|
138
140
|
/** For a few seconds after coming back to the pane, LAST is tinted by each row's move since the user left. */
|
|
139
141
|
sinceLastSeen?: SinceLastSeenRecap | null;
|
|
@@ -155,21 +157,26 @@ export function PortfolioTickerTable({
|
|
|
155
157
|
openPerpetualRef.current = onOpenPerpetual;
|
|
156
158
|
const sparklineVisible = columns.some((column) => column.id === PRICE_SPARKLINE_COLUMN_ID);
|
|
157
159
|
const tableFinancialsMap = useLiveSparklineFinancials(financialsMap, sortedTickers, sparklineVisible);
|
|
160
|
+
const noteColumnId = columns.some((column) => column.id === "name") ? "name" : "ticker";
|
|
161
|
+
const hasFrontReference = useMemo(() => sortedTickers.some((ticker) => columnContext.futuresTerms?.get(ticker.metadata.ticker)?.front),
|
|
162
|
+
[columnContext.futuresTerms, sortedTickers]);
|
|
158
163
|
const resolveCell = useCallback(
|
|
159
164
|
(column: ColumnConfig, ticker: TickerRecord, financials: TickerFinancials | undefined) => {
|
|
160
165
|
const key = `${ticker.metadata.ticker}:${column.id}`;
|
|
161
166
|
const version = buildCellVersion(column, ticker, financials, columnContext);
|
|
162
|
-
const
|
|
167
|
+
const cached = cellCacheRef.current.get(key, version, () => {
|
|
163
168
|
const cell = getColumnValue(column, ticker, financials, columnContext);
|
|
164
169
|
const marketId = PERPETUAL_COLUMN_IDS.has(column.id) ? columnContext.perpetuals?.get(ticker.metadata.ticker)?.marketId : undefined;
|
|
165
170
|
return marketId ? { ...cell, onPress: () => openPerpetualRef.current?.(marketId) } : cell;
|
|
166
171
|
});
|
|
172
|
+
const note = column.id === noteColumnId ? futuresFrontNotes?.get(ticker.metadata.ticker) : undefined;
|
|
173
|
+
const cell = note ? { ...cached, note } : cached;
|
|
167
174
|
// LAST keeps its price: a move since the user last looked only tints the cell.
|
|
168
175
|
if (column.id !== "price" || !sinceLastSeen) return cell;
|
|
169
176
|
const move = sinceLastSeen.moves.get(ticker.metadata.ticker);
|
|
170
177
|
return move == null ? cell : { ...cell, backgroundColor: sinceLastSeenTint(move, sinceLastSeen.fading) };
|
|
171
178
|
},
|
|
172
|
-
[columnContext, sinceLastSeen],
|
|
179
|
+
[columnContext, futuresFrontNotes, noteColumnId, sinceLastSeen],
|
|
173
180
|
);
|
|
174
181
|
const pnlColumn = columns.find((column) => column.id === "pnl" || column.id === "pnl_pct");
|
|
175
182
|
// Reads the same cached P&L cells the rows show; only changed rows recompute.
|
|
@@ -202,7 +209,8 @@ export function PortfolioTickerTable({
|
|
|
202
209
|
: pnlLabel !== "P&L" && (column.id === "pnl" || column.id === "pnl_pct") ? {
|
|
203
210
|
...column,
|
|
204
211
|
label: column.id === "pnl_pct" ? pnlLabel.replace("P&L", "%") : pnlLabel,
|
|
205
|
-
} : column)
|
|
212
|
+
} : column).map((column) => hasFrontReference && column.id === noteColumnId ? { ...column, flexGrow: 1 } : column),
|
|
213
|
+
[columns, grossWeights, hasFrontReference, hasNonShareQuantity, noteColumnId, pnlLabel, sharesWidth, tickerWidth]);
|
|
206
214
|
// The table offers "Sort by…" while it has a sort column. A watchlist starts
|
|
207
215
|
// unsorted and a third header click turns sorting off, so the unsorted state
|
|
208
216
|
// needs its own entry until TickerListTableView takes `sortable`.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createTtlCache } from "../../../data/ttl-cache";
|
|
1
2
|
import { httpFetch } from "../../../utils/http-transport";
|
|
2
3
|
import { isoDateOrNull, numericOrNull, recordOrNull, textOrNull } from "../../../utils/guards";
|
|
3
4
|
import {
|
|
@@ -84,20 +85,14 @@ async function readJson(url: string): Promise<unknown> {
|
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
/** The series break changes about every two years; one read serves the session. */
|
|
87
|
-
|
|
88
|
+
const seriesBreaks = createTtlCache<string>({ ttlMs: Infinity });
|
|
88
89
|
|
|
89
90
|
function currentSeriesBreak(force: boolean): Promise<string> {
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
});
|
|
96
|
-
seriesBreakRead = read;
|
|
97
|
-
// A failed read is not kept, so the next one asks again.
|
|
98
|
-
read.catch(() => { if (seriesBreakRead === read) seriesBreakRead = null; });
|
|
99
|
-
}
|
|
100
|
-
return seriesBreakRead;
|
|
91
|
+
return seriesBreaks.load("current", async () => {
|
|
92
|
+
const seriesBreak = newestSeriesBreak(parseSeriesBreaks(await readJson(`${API}/list/seriesbreaks.json`)));
|
|
93
|
+
if (!seriesBreak) throw new Error("No series break was listed.");
|
|
94
|
+
return seriesBreak;
|
|
95
|
+
}, { force });
|
|
101
96
|
}
|
|
102
97
|
|
|
103
98
|
/**
|
|
@@ -117,31 +112,11 @@ export async function fetchPrimaryDealers(): Promise<DealerBoard> {
|
|
|
117
112
|
};
|
|
118
113
|
}
|
|
119
114
|
|
|
120
|
-
|
|
121
|
-
at: number;
|
|
122
|
-
read: Promise<SeriesPoint[]>;
|
|
123
|
-
points: SeriesPoint[] | null;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const seriesReads = new Map<string, SeriesRead>();
|
|
115
|
+
const seriesReads = createTtlCache<SeriesPoint[]>({ ttlMs: HISTORY_TTL_MS });
|
|
127
116
|
|
|
128
117
|
function seriesRead(seriesBreak: string, key: string, force: boolean): Promise<SeriesPoint[]> {
|
|
129
|
-
const id = `${seriesBreak}:${key}`;
|
|
130
|
-
const known = seriesReads.get(id);
|
|
131
|
-
if (known && !force && Date.now() - known.at < HISTORY_TTL_MS) return known.read;
|
|
132
118
|
const url = `${API}/get/${encodeURIComponent(seriesBreak)}/timeseries/${encodeURIComponent(key)}.json`;
|
|
133
|
-
|
|
134
|
-
at: Date.now(),
|
|
135
|
-
points: null,
|
|
136
|
-
read: readJson(url).then((payload) => {
|
|
137
|
-
entry.points = parseSeriesPoints(payload);
|
|
138
|
-
return entry.points;
|
|
139
|
-
}),
|
|
140
|
-
};
|
|
141
|
-
seriesReads.set(id, entry);
|
|
142
|
-
// A failed read is not kept, so the next one asks again.
|
|
143
|
-
entry.read.catch(() => { if (seriesReads.get(id) === entry) seriesReads.delete(id); });
|
|
144
|
-
return entry.read;
|
|
119
|
+
return seriesReads.load(`${seriesBreak}:${key}`, async () => parseSeriesPoints(await readJson(url)), { force });
|
|
145
120
|
}
|
|
146
121
|
|
|
147
122
|
/** A row's history, one read per series behind it, each kept for the session. */
|
|
@@ -158,9 +133,9 @@ export async function loadDealerHistory(
|
|
|
158
133
|
function heldSeries(seriesBreak: string, row: Pick<DealerRow, "keys">): SeriesPoint[][] | null {
|
|
159
134
|
const series: SeriesPoint[][] = [];
|
|
160
135
|
for (const key of row.keys) {
|
|
161
|
-
const
|
|
162
|
-
if (!
|
|
163
|
-
series.push(
|
|
136
|
+
const points = seriesReads.peek(`${seriesBreak}:${key}`);
|
|
137
|
+
if (!points) return null;
|
|
138
|
+
series.push(points);
|
|
164
139
|
}
|
|
165
140
|
return series;
|
|
166
141
|
}
|
|
@@ -178,6 +153,6 @@ export function cachedDealerHistory(seriesBreak: string, row: Pick<DealerRow, "i
|
|
|
178
153
|
|
|
179
154
|
/** Forgets what was read, for a test or a refresh that must see the release again. */
|
|
180
155
|
export function resetPrimaryDealersCache(): void {
|
|
181
|
-
|
|
156
|
+
seriesBreaks.clear();
|
|
182
157
|
seriesReads.clear();
|
|
183
158
|
}
|
|
@@ -4,7 +4,7 @@ import { curveStrip, curveSurfaceMinRows } from "../../../components/chart/curve
|
|
|
4
4
|
import { isAccessDenied } from "../../../api-client/errors";
|
|
5
5
|
import type { RateContract, RateMeeting } from "../../../api-client/rates";
|
|
6
6
|
import { useAsyncResource, usePluginPaneState } from "../../../public/react";
|
|
7
|
-
import { blendHex, colors,
|
|
7
|
+
import { blendHex, colors, shownPriceColor } from "../../../theme/colors";
|
|
8
8
|
import type { PaneProps } from "../../../types/plugin";
|
|
9
9
|
import { Box } from "../../../ui";
|
|
10
10
|
import { nextHeaderSort, type SortDirection } from "../../../utils/sort-values";
|
|
@@ -54,7 +54,7 @@ function timestamp(value: string | null): string { return value?.replace("T", "
|
|
|
54
54
|
function percentile(value: number | null): string { return value == null ? "--" : value.toFixed(0); }
|
|
55
55
|
/** Hikes up, cuts down, by the sign the text shows, so a move that rounds to zero stays neutral. */
|
|
56
56
|
function signColor(value: number | null, digits: number): string {
|
|
57
|
-
return value == null || !Number.isFinite(value) ? colors.textDim :
|
|
57
|
+
return value == null || !Number.isFinite(value) ? colors.textDim : shownPriceColor(value, digits);
|
|
58
58
|
}
|
|
59
59
|
function bpText(value: number | null): string { return value == null ? "--" : rateChangeText(value / 100); }
|
|
60
60
|
/** Days to a meeting, counted in UTC dates like the schedule. */
|
|
@@ -21,13 +21,13 @@ export const realizedVolModule: PluginModule = {
|
|
|
21
21
|
{ ...createTickerSurfacePaneTemplate({
|
|
22
22
|
id: "realized-vol-graph-pane", paneId: "realized-vol", label: "Realized Volatility Graph",
|
|
23
23
|
description: "Rolling realized volatility, price and a dated current ATM IV reference.",
|
|
24
|
-
keywords: ["hvg", "realized", "historical", "volatility", "gv"], shortcut: "HVG", publicShare: true,
|
|
24
|
+
keywords: ["hvg", "realized", "historical", "volatility", "vol", "gv"], shortcut: "HVG", publicShare: true,
|
|
25
25
|
viewKey: "graph", settings: () => ({ initialView: "graph" }),
|
|
26
26
|
}), headless: realizedVolHeadless("graph") },
|
|
27
27
|
{ ...createTickerSurfacePaneTemplate({
|
|
28
28
|
id: "realized-vol-table-pane", paneId: "realized-vol", label: "Realized Volatility Cone",
|
|
29
29
|
description: "Current realized volatility against its historical range and percentile.",
|
|
30
|
-
keywords: ["hvt", "volatility", "cone", "percentile"], shortcut: "HVT", publicShare: true,
|
|
30
|
+
keywords: ["hvt", "volatility", "vol", "cone", "percentile"], shortcut: "HVT", publicShare: true,
|
|
31
31
|
viewKey: "cone", settings: () => ({ initialView: "cone" }),
|
|
32
32
|
}), headless: realizedVolHeadless("cone") },
|
|
33
33
|
],
|
|
@@ -109,7 +109,7 @@ export function RealizedVolPane({ width, height, focused }: PaneProps) {
|
|
|
109
109
|
const refreshedCurrent = refreshedIv?.owner === ivLoader && refreshedIv.at > (loadedIv.updatedAt ?? 0) ? refreshedIv.data : null;
|
|
110
110
|
const iv = { ...loadedIv, data: refreshedCurrent ?? loadedIv.data };
|
|
111
111
|
ivDataRef.current = iv.data;
|
|
112
|
-
useLiveSessionRefresh(refreshIv, ATM_IV_REFRESH_MS, showIv && spotAvailable && !!iv.data?.reference && !loadedIv.loading,
|
|
112
|
+
const ivRefresh = useLiveSessionRefresh(refreshIv, ATM_IV_REFRESH_MS, showIv && spotAvailable && !!iv.data?.reference && !loadedIv.loading,
|
|
113
113
|
loadedIv.updatedAt);
|
|
114
114
|
const model = useMemo(() => history.data ? projectRealizedVolatility(history.data.history, {
|
|
115
115
|
symbol: history.data.symbol, estimator, windows, lookbackYears: Number(lookback) === 2 ? 2 : 1,
|
|
@@ -117,7 +117,7 @@ export function RealizedVolPane({ width, height, focused }: PaneProps) {
|
|
|
117
117
|
const chartInput = useMemo(() => model ? { history: model.history, rolling: model.series, windows: model.windows,
|
|
118
118
|
priceUnit: priceSeriesUnit(quote?.currency ?? "", quote?.instrumentType), iv: showIv ? iv.data?.reference ?? null : null } : null,
|
|
119
119
|
[model, quote?.currency, quote?.instrumentType, showIv, iv.data]);
|
|
120
|
-
const notices = [identityError, history.error, history.data?.error, iv.error, iv.data?.error,
|
|
120
|
+
const notices = [identityError, history.error, history.data?.error, iv.error, ivRefresh.error, iv.data?.error,
|
|
121
121
|
...(model?.warnings ?? []), ...(showIv ? iv.data?.warnings ?? [] : []),
|
|
122
122
|
...(showIv && symbol && !spotAvailable ? ["Current ATM IV unavailable: underlying quote is missing or stale"] : []),
|
|
123
123
|
].filter((value): value is string => !!value);
|
|
@@ -161,7 +161,7 @@ export function RealizedVolPane({ width, height, focused }: PaneProps) {
|
|
|
161
161
|
stale: history.data?.stale ?? false, source: history.data?.source ?? null,
|
|
162
162
|
asOf: model?.asOf?.toISOString() ?? null,
|
|
163
163
|
errors: [...new Set([history.error, history.data?.error, ...(model?.warnings ?? []),
|
|
164
|
-
...(showIv ? [iv.error, iv.data?.error, !iv.data?.reference ? "Current ATM IV unavailable" : null] : []),
|
|
164
|
+
...(showIv ? [iv.error, ivRefresh.error, iv.data?.error, !iv.data?.reference ? "Current ATM IV unavailable" : null] : []),
|
|
165
165
|
].filter((value): value is string => !!value))],
|
|
166
166
|
currentIv: showIv && iv.data?.reference ? { value: iv.data.reference.value * 100,
|
|
167
167
|
date: iv.data.reference.date.toISOString(), label: iv.data.reference.label,
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
} from "../../../api-client/types";
|
|
8
8
|
import { createPluginCache } from "../../../data/plugin-cache";
|
|
9
9
|
import { isAccessDenied } from "../../../api-client/errors";
|
|
10
|
+
import { quoteTradingExchange } from "../../../market-data/quotes/metadata";
|
|
10
11
|
import { cachedCloudResource, loadCloudResource } from "../shared/cloud-resource";
|
|
11
12
|
import {
|
|
12
13
|
buildRotation,
|
|
@@ -75,6 +76,10 @@ export function validateRotationHistory(
|
|
|
75
76
|
throw new Error("Invalid daily history.");
|
|
76
77
|
return response.data;
|
|
77
78
|
}
|
|
79
|
+
/** The venue a quote is for, as `quote` names it; empty when it names none. */
|
|
80
|
+
function listingVenue(quote: CloudQuotePayload | undefined): string {
|
|
81
|
+
return quote ? quoteTradingExchange(quote) : "";
|
|
82
|
+
}
|
|
78
83
|
export async function fetchRotation(
|
|
79
84
|
benchmark: RotationInstrument,
|
|
80
85
|
instruments: RotationInstrument[],
|
|
@@ -106,6 +111,16 @@ export async function fetchRotation(
|
|
|
106
111
|
if (isAccessDenied(error)) throw error;
|
|
107
112
|
quoteError = "Listing currencies unavailable.";
|
|
108
113
|
}
|
|
114
|
+
// A benchmark typed without its exchange (ACWI) is the listing its quote is for (ACWI:NASDAQ), as `quote`
|
|
115
|
+
// reads it: that venue's calendar picks the weekly closes.
|
|
116
|
+
const venue = benchmark.exchange
|
|
117
|
+
? ""
|
|
118
|
+
: listingVenue(quotes.get(rotationId(benchmark)));
|
|
119
|
+
const reference = venue ? { ...benchmark, exchange: venue } : benchmark;
|
|
120
|
+
const requested = unique.map((row) => ({
|
|
121
|
+
id: rotationId(row),
|
|
122
|
+
instrument: rotationId(row) === rotationId(benchmark) ? reference : row,
|
|
123
|
+
}));
|
|
109
124
|
const start = new Date(now);
|
|
110
125
|
start.setUTCFullYear(start.getUTCFullYear() - 3);
|
|
111
126
|
start.setUTCDate(1);
|
|
@@ -115,10 +130,9 @@ export async function fetchRotation(
|
|
|
115
130
|
let next = 0;
|
|
116
131
|
// Bound Cloud history requests; shared server caches batch upstream demand.
|
|
117
132
|
await Promise.all(
|
|
118
|
-
Array.from({ length: Math.min(4,
|
|
119
|
-
while (next <
|
|
120
|
-
const instrument =
|
|
121
|
-
id = rotationId(instrument),
|
|
133
|
+
Array.from({ length: Math.min(4, requested.length) }, async () => {
|
|
134
|
+
while (next < requested.length) {
|
|
135
|
+
const { id, instrument } = requested[next++]!,
|
|
122
136
|
currency = quotes.get(id)?.currency ?? null;
|
|
123
137
|
try {
|
|
124
138
|
const response = await client.getCloudHistory(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { HeadlessPaneDefinition } from "../../../types/plugin";
|
|
2
2
|
import { fetchRotation } from "./client";
|
|
3
|
-
import { rotationInstruments, sectorRotationInstruments } from "./model";
|
|
3
|
+
import { DEFAULT_ROTATION_BENCHMARK, rotationInstruments, sectorRotationInstruments } from "./model";
|
|
4
4
|
|
|
5
5
|
/** Same rounding as the pane: ratios to 2 decimals, percentiles whole. */
|
|
6
6
|
const fixed = (value: unknown, digits: number) =>
|
|
@@ -21,7 +21,7 @@ export const rotationHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
21
21
|
key: "benchmark",
|
|
22
22
|
type: "string",
|
|
23
23
|
description: "Benchmark listing.",
|
|
24
|
-
defaultValue:
|
|
24
|
+
defaultValue: DEFAULT_ROTATION_BENCHMARK,
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
key: "trail",
|
|
@@ -44,7 +44,7 @@ export const rotationHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
44
44
|
},
|
|
45
45
|
async load(args, ctx) {
|
|
46
46
|
const references = rotationInstruments(
|
|
47
|
-
String(args.options.benchmark ??
|
|
47
|
+
String(args.options.benchmark ?? DEFAULT_ROTATION_BENCHMARK),
|
|
48
48
|
);
|
|
49
49
|
if (references.length !== 1) throw new Error("Choose one benchmark.");
|
|
50
50
|
const instruments = args.symbols.length
|
|
@@ -3,6 +3,7 @@ import type { PluginModule } from "../plugin-module";
|
|
|
3
3
|
import { rotationCache } from "./client";
|
|
4
4
|
import { rotationHeadless } from "./headless";
|
|
5
5
|
import { RelativeRotationPane } from "./pane";
|
|
6
|
+
import { DEFAULT_ROTATION_BENCHMARK } from "./model";
|
|
6
7
|
|
|
7
8
|
export const relativeRotationModule: PluginModule = {
|
|
8
9
|
panes: [
|
|
@@ -41,7 +42,7 @@ export const relativeRotationModule: PluginModule = {
|
|
|
41
42
|
key: "benchmark",
|
|
42
43
|
label: "Benchmark",
|
|
43
44
|
type: "text",
|
|
44
|
-
placeholder:
|
|
45
|
+
placeholder: DEFAULT_ROTATION_BENCHMARK,
|
|
45
46
|
},
|
|
46
47
|
{
|
|
47
48
|
key: "trail",
|
|
@@ -2,6 +2,7 @@ import { publishedWeekClose } from "../analytics/sharpe-cadence";
|
|
|
2
2
|
import { historyStatistics } from "../../../components/chart/curve/model";
|
|
3
3
|
import type { CloudPricePointPayload } from "../../../api-client/types";
|
|
4
4
|
import { getSectorCollection } from "../sectors/sector-data";
|
|
5
|
+
import { rotationQuadrant, rotationSeries, type RotationQuadrantName } from "../alerts/metric-model";
|
|
5
6
|
import {
|
|
6
7
|
canonicalExchange,
|
|
7
8
|
normalizeSymbol,
|
|
@@ -11,6 +12,8 @@ import {
|
|
|
11
12
|
const DAY = 86_400_000;
|
|
12
13
|
const WEEK = 7 * DAY;
|
|
13
14
|
export const ROTATION_LIMIT = 24;
|
|
15
|
+
/** RRG's benchmark when none is chosen; RRG alerts default to it too. */
|
|
16
|
+
export const DEFAULT_ROTATION_BENCHMARK = "SPY:NYSEARCA";
|
|
14
17
|
export interface RotationInstrument {
|
|
15
18
|
symbol: string;
|
|
16
19
|
exchange: string;
|
|
@@ -46,8 +49,7 @@ export interface RotationRow {
|
|
|
46
49
|
asOf: string | null;
|
|
47
50
|
strength: number | null;
|
|
48
51
|
momentum: number | null;
|
|
49
|
-
quadrant:
|
|
50
|
-
"Leading" | "Weakening" | "Lagging" | "Improving" | "Neutral" | null;
|
|
52
|
+
quadrant: RotationQuadrantName | null;
|
|
51
53
|
strengthRank: RotationRank;
|
|
52
54
|
momentumRank: RotationRank;
|
|
53
55
|
relativeReturn13w: number | null;
|
|
@@ -149,21 +151,8 @@ function rotationRank(
|
|
|
149
151
|
lastDate: stats.endDate,
|
|
150
152
|
};
|
|
151
153
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
momentum: number | null,
|
|
155
|
-
): RotationRow["quadrant"] {
|
|
156
|
-
if (strength == null || momentum == null) return null;
|
|
157
|
-
if (Math.abs(strength - 100) < 1e-9 || Math.abs(momentum - 100) < 1e-9)
|
|
158
|
-
return "Neutral";
|
|
159
|
-
return strength > 100
|
|
160
|
-
? momentum > 100
|
|
161
|
-
? "Leading"
|
|
162
|
-
: "Weakening"
|
|
163
|
-
: momentum > 100
|
|
164
|
-
? "Improving"
|
|
165
|
-
: "Lagging";
|
|
166
|
-
}
|
|
154
|
+
/** The weekly strength, momentum and quadrant math is the RRG alert's, shared with Gloom Cloud's delivery. */
|
|
155
|
+
export { rotationQuadrant };
|
|
167
156
|
export function buildRotation(
|
|
168
157
|
benchmark: RotationHistory,
|
|
169
158
|
instruments: RotationHistory[],
|
|
@@ -219,10 +208,12 @@ export function buildRotation(
|
|
|
219
208
|
const benchmarkUsable = !!benchmark.currency && !!asOf;
|
|
220
209
|
const rows = instruments.map((source): RotationRow => {
|
|
221
210
|
const closes = rotationCloses(source.points, through);
|
|
211
|
+
// A benchmark with no usable weeks says so once in `gaps`; its rows are not to blame for it.
|
|
222
212
|
const gaps = [
|
|
223
213
|
source.error,
|
|
224
214
|
source.stale ? "History is stale." : null,
|
|
225
|
-
|
|
215
|
+
benchmarkUsable &&
|
|
216
|
+
(source.currency !== benchmark.currency || !source.currency)
|
|
226
217
|
? "Price currency does not match the benchmark."
|
|
227
218
|
: null,
|
|
228
219
|
].filter((x): x is string => !!x);
|
|
@@ -240,34 +231,16 @@ export function buildRotation(
|
|
|
240
231
|
? ratio
|
|
241
232
|
: null;
|
|
242
233
|
});
|
|
243
|
-
const
|
|
244
|
-
const window = ratios.slice(Math.max(0, index - 12), index + 1);
|
|
245
|
-
if (
|
|
246
|
-
ratio == null ||
|
|
247
|
-
window.length !== 13 ||
|
|
248
|
-
window.some((value) => value == null)
|
|
249
|
-
)
|
|
250
|
-
return null;
|
|
251
|
-
const mean =
|
|
252
|
-
(window as number[]).reduce((sum, value) => sum + value, 0) / 13;
|
|
253
|
-
return (100 * ratio) / mean;
|
|
254
|
-
});
|
|
234
|
+
const series = rotationSeries(ratios);
|
|
255
235
|
const history = weeks.map((week, index): RotationPoint => {
|
|
256
|
-
const strength =
|
|
257
|
-
previous = index >= 4 ? (strengths[index - 4] ?? null) : null;
|
|
258
|
-
const contiguous =
|
|
259
|
-
index >= 4 &&
|
|
260
|
-
strengths.slice(index - 4, index + 1).every((value) => value != null);
|
|
236
|
+
const { strength, momentum } = series[index]!;
|
|
261
237
|
const priorRatio = index >= 13 ? (ratios[index - 13] ?? null) : null,
|
|
262
238
|
ratio = ratios[index] ?? null;
|
|
263
239
|
return {
|
|
264
240
|
date: weekly.get(week)?.date ?? week,
|
|
265
241
|
week,
|
|
266
242
|
strength,
|
|
267
|
-
momentum
|
|
268
|
-
strength != null && previous != null && contiguous
|
|
269
|
-
? (100 * strength) / previous
|
|
270
|
-
: null,
|
|
243
|
+
momentum,
|
|
271
244
|
relativeReturn13w:
|
|
272
245
|
ratio != null &&
|
|
273
246
|
priorRatio != null &&
|
|
@@ -284,7 +257,10 @@ export function buildRotation(
|
|
|
284
257
|
Number.isFinite(latest.strength) &&
|
|
285
258
|
latest.momentum != null &&
|
|
286
259
|
Number.isFinite(latest.momentum);
|
|
287
|
-
if (
|
|
260
|
+
if (
|
|
261
|
+
benchmarkUsable &&
|
|
262
|
+
(!latest || latest.strength == null || latest.momentum == null)
|
|
263
|
+
)
|
|
288
264
|
gaps.push(
|
|
289
265
|
"At least 17 contiguous matched weekly closes are required; a missing week breaks the trail.",
|
|
290
266
|
);
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
import { useResearchCloudSession } from "../shared/research-cloud-session";
|
|
37
37
|
import { cachedRotation, loadRotation } from "./client";
|
|
38
38
|
import {
|
|
39
|
+
DEFAULT_ROTATION_BENCHMARK,
|
|
39
40
|
ROTATION_LIMIT,
|
|
40
41
|
rotationId,
|
|
41
42
|
rotationTrailWeeks,
|
|
@@ -140,13 +141,13 @@ function RotationDetail({
|
|
|
140
141
|
export function RelativeRotationPane(props: PaneProps) {
|
|
141
142
|
const [scope] = usePaneSettingValue("scope", "sectors");
|
|
142
143
|
const [symbols] = usePaneSettingValue("symbols", "");
|
|
143
|
-
const [benchmarkText] = usePaneSettingValue("benchmark",
|
|
144
|
+
const [benchmarkText] = usePaneSettingValue("benchmark", DEFAULT_ROTATION_BENCHMARK);
|
|
144
145
|
const [trailText] = usePaneSettingValue("trail", "6");
|
|
145
146
|
const { collectionId } = usePaneCollection();
|
|
146
147
|
const tickers = useTickers();
|
|
147
148
|
const context = useMemo(() => {
|
|
148
149
|
try {
|
|
149
|
-
const references = rotationInstruments(benchmarkText ||
|
|
150
|
+
const references = rotationInstruments(benchmarkText || DEFAULT_ROTATION_BENCHMARK);
|
|
150
151
|
if (references.length !== 1)
|
|
151
152
|
throw new Error("Choose one benchmark in pane settings.");
|
|
152
153
|
const instruments =
|