gloomberb 0.17.4 → 0.17.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -1
- package/patches/electrobun@1.18.1.patch +80 -0
- package/src/api-client/chat.ts +9 -0
- package/src/api-client/commodity-balances.ts +77 -0
- package/src/api-client/data.ts +35 -3
- package/src/api-client/earnings.ts +10 -0
- package/src/api-client/index.ts +5 -0
- package/src/api-client/insider-signals.ts +75 -0
- package/src/api-client/market-discovery.ts +1 -1
- package/src/api-client/normalizers.ts +1 -0
- package/src/api-client/paths.ts +9 -0
- package/src/api-client/research-activity.ts +10 -3
- package/src/api-client/socket.ts +10 -0
- package/src/api-client/types.ts +25 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +2 -1
- package/src/app/runtime/broker-import.ts +23 -21
- package/src/app/runtime/plugin-bindings.ts +13 -7
- package/src/app/runtime/update.ts +64 -4
- package/src/app.tsx +2 -0
- package/src/architecture/core-builtin-allowlist.json +2 -10
- package/src/ask-gloom/pane-context.ts +11 -1
- package/src/brokers/broker-portfolio-sync.ts +66 -0
- package/src/brokers/sync-broker-instance.ts +75 -41
- package/src/cli/commands/market.ts +101 -34
- package/src/cli/commands/options-chain.ts +20 -6
- package/src/cli/commands/overview.ts +54 -13
- package/src/cli/commands/search.ts +1 -1
- package/src/cli/commands/system.ts +19 -1
- package/src/cli/commands/ticker.ts +24 -4
- package/src/cli/context.ts +13 -0
- package/src/cli/errors.ts +15 -2
- package/src/cli/fx-pair.ts +77 -0
- package/src/cli/glossary.ts +34 -4
- package/src/cli/history-rows.ts +25 -11
- package/src/cli/index.ts +30 -12
- package/src/cli/listing-arg.ts +61 -138
- package/src/cli/listing-suggestion.ts +3 -21
- package/src/cli/pane-functions/catalog.ts +50 -0
- package/src/cli/pane-functions/freshness.ts +44 -9
- package/src/cli/pane-functions/function-help.ts +9 -9
- package/src/cli/pane-functions/headless.ts +19 -7
- package/src/cli/pane-functions/index.ts +63 -5
- package/src/cli/pane-functions/market-session.ts +18 -3
- package/src/cli/pane-functions/resolver.ts +29 -8
- package/src/cli/pane-functions/screenshot.ts +2 -0
- package/src/cli/report-tables.ts +11 -4
- package/src/cli/result.ts +2 -0
- package/src/cli/series-search.ts +6 -1
- package/src/cli/ticker-resolution.ts +1 -2
- package/src/cli/unknown-command.ts +18 -0
- package/src/components/chart/composite/composite-chart.tsx +80 -19
- package/src/components/chart/composite/format.ts +17 -0
- package/src/components/chart/composite/legend.tsx +26 -15
- package/src/components/chart/composite/panel-labels.ts +38 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +25 -7
- package/src/components/chart/composite/price-axis-labels.tsx +16 -3
- package/src/components/chart/composite/scene.ts +60 -2
- package/src/components/chart/composite/series-axis-tags.tsx +67 -0
- package/src/components/chart/composite/tools.ts +2 -2
- package/src/components/chart/composite/types.ts +28 -0
- package/src/components/chart/core/view-reset.ts +46 -0
- package/src/components/command-bar/commands/collection.ts +1 -1
- package/src/components/command-bar/commands/direct/index.ts +48 -1
- package/src/components/command-bar/commands/registry.ts +8 -0
- package/src/components/command-bar/list/model.ts +7 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/panel/prompt-binding.ts +8 -0
- package/src/components/command-bar/routes/root/command-items.ts +17 -2
- package/src/components/command-bar/routes/root/pick-prefetch.ts +38 -0
- package/src/components/command-bar/routes/root/provider-search.ts +6 -0
- package/src/components/command-bar/routes/root/runtime.ts +3 -0
- package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -1
- package/src/components/command-bar/routes/root/shortcut-items.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/actions.ts +34 -0
- package/src/components/command-bar/surface/index.tsx +7 -1
- package/src/components/command-bar/workflow/collection-actions.ts +6 -5
- package/src/components/command-bar/workflow/ops.ts +8 -5
- package/src/components/command-bar/workflow/pane-reuse.ts +3 -2
- package/src/components/command-bar/workflow/tickers.ts +1 -1
- package/src/components/form-modal/confirm.tsx +1 -0
- package/src/components/form-modal/request.ts +2 -0
- package/src/components/layout/detached-pane-shell.tsx +6 -1
- package/src/components/layout/pane/content.tsx +11 -8
- package/src/components/layout/pane/sidebar.tsx +24 -11
- package/src/components/layout/pane/ticker-fate-gate.tsx +56 -0
- package/src/components/layout/shell/index.tsx +5 -1
- package/src/components/layout/shell/menu.ts +8 -0
- package/src/components/layout/shell/pane/capture.ts +29 -12
- package/src/components/onboarding/wizard-model.ts +1 -1
- package/src/components/onboarding/wizard-positions.ts +18 -10
- package/src/components/ticker/badge/format.ts +64 -15
- package/src/components/ticker/badge/index.tsx +32 -11
- package/src/components/ticker/badge/list.tsx +4 -3
- package/src/components/ticker/badge/text.tsx +2 -2
- package/src/components/ticker/list-table-view.tsx +8 -1
- package/src/components/ui/confirm-dialog.tsx +30 -4
- package/src/components/ui/data-table/opentui/index.tsx +61 -20
- package/src/components/ui/data-table/opentui/model.ts +104 -0
- package/src/components/ui/fields.tsx +5 -0
- package/src/components/ui/inline-quick-add.tsx +4 -1
- package/src/components/ui/query-bar-keys.tsx +29 -5
- package/src/components/ui/query-bar.tsx +29 -4
- package/src/components/ui/windowed-list.tsx +27 -0
- package/src/data/config/store/normalize.ts +8 -0
- package/src/docs/snapshot.json +248 -5
- package/src/i18n/es.ts +3 -1
- package/src/i18n/ja.ts +3 -1
- package/src/i18n/ko.ts +3 -1
- package/src/i18n/zh-cn.ts +3 -1
- package/src/i18n/zh-tw.ts +3 -1
- package/src/layout-marketplace/cloud.ts +16 -5
- package/src/layout-marketplace/gallery.tsx +4 -4
- package/src/layout-marketplace/linked-sync.ts +9 -3
- package/src/layout-marketplace/linked.ts +24 -3
- package/src/layout-marketplace/model.ts +39 -4
- package/src/market-data/coordinator/events.ts +5 -1
- package/src/market-data/coordinator/index.ts +38 -1
- package/src/market-data/coordinator/quotes.ts +4 -0
- package/src/market-data/futures-contract.ts +53 -0
- package/src/market-data/hooks.tsx +225 -30
- package/src/market-data/market/bar-period.ts +71 -0
- package/src/market-data/market/format.ts +14 -2
- package/src/market-data/market/freshness.ts +28 -1
- package/src/market-data/market/status.ts +152 -13
- package/src/market-data/model-dividend-yield.ts +76 -0
- package/src/market-data/quotes/freshness.ts +30 -1
- package/src/{plugins/builtin/portfolio-list → market-data/quotes}/live-valuation.ts +6 -6
- package/src/market-data/quotes/regular-session-valuation.ts +32 -1
- package/src/plugins/builtin/analytics/metrics.ts +8 -4
- package/src/plugins/builtin/analytics/risk-headless.ts +12 -5
- package/src/plugins/builtin/analytics/risk-model.ts +74 -15
- package/src/plugins/builtin/analytics/risk-pane.tsx +6 -6
- package/src/plugins/builtin/broker-manager/detail.tsx +2 -2
- package/src/plugins/builtin/broker-manager/model.ts +2 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +0 -1
- package/src/plugins/builtin/chart-composer/editor/controller.ts +14 -10
- package/src/plugins/builtin/chart-composer/formula.ts +233 -0
- package/src/plugins/builtin/chart-composer/headless.ts +27 -2
- package/src/plugins/builtin/chart-composer/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/pane.tsx +41 -10
- package/src/plugins/builtin/chart-composer/presets.ts +91 -2
- package/src/plugins/builtin/chart-composer/quick-add.tsx +9 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +44 -11
- package/src/plugins/builtin/chart-composer/series-expression.ts +1 -1
- package/src/plugins/builtin/chart-composer/settings.ts +16 -4
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/content/channel-navigation.ts +17 -6
- package/src/plugins/builtin/chat/content/footer.tsx +5 -2
- package/src/plugins/builtin/chat/content/index.tsx +12 -5
- package/src/plugins/builtin/chat/content/mentions.ts +50 -21
- package/src/plugins/builtin/chat/content/scroll.ts +12 -5
- package/src/plugins/builtin/chat/content/snapshot.ts +11 -0
- package/src/plugins/builtin/chat/content/transcript.tsx +48 -37
- package/src/plugins/builtin/chat/content/whole-message-copy.ts +44 -2
- package/src/plugins/builtin/chat/controller/index.ts +88 -17
- package/src/plugins/builtin/chat/controller/mention-candidates.ts +104 -0
- package/src/plugins/builtin/chat/controller/messages.ts +14 -2
- package/src/plugins/builtin/chat/controller/realtime.ts +9 -0
- package/src/plugins/builtin/chat/controller/state.ts +25 -0
- package/src/plugins/builtin/chat/controller/unread-people.ts +240 -0
- package/src/plugins/builtin/chat/controller/view.ts +4 -0
- package/src/plugins/builtin/chat/conversation-recency.ts +73 -0
- package/src/plugins/builtin/chat/conversations.ts +12 -5
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/layout.ts +23 -5
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
- package/src/plugins/builtin/chat/share/flow.tsx +18 -9
- package/src/plugins/builtin/chat/sidebar-rows.ts +18 -5
- package/src/plugins/builtin/chat/sidebar.tsx +6 -1
- package/src/plugins/builtin/chat/unread-badge.ts +38 -7
- package/src/plugins/builtin/cloud/askg/manifest.ts +4 -3
- package/src/plugins/builtin/cloud/askg/pane-attachment.ts +2 -3
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +16 -5
- package/src/plugins/builtin/commodities-energy/index.tsx +23 -4
- package/src/plugins/builtin/commodity-balances/client.ts +51 -193
- package/src/plugins/builtin/commodity-balances/headless.ts +10 -3
- package/src/plugins/builtin/commodity-balances/model.ts +5 -180
- package/src/plugins/builtin/commodity-balances/pane.tsx +3 -0
- package/src/plugins/builtin/custom-view/index.tsx +1 -1
- package/src/plugins/builtin/custom-view/loader.ts +57 -7
- package/src/plugins/builtin/custom-view/sources.ts +146 -0
- package/src/plugins/builtin/custom-view/view-spec.ts +37 -1
- package/src/plugins/builtin/earnings/board-model.ts +33 -3
- package/src/plugins/builtin/earnings/board-table.ts +59 -14
- package/src/plugins/builtin/earnings/board.tsx +36 -3
- package/src/plugins/builtin/earnings/headless.ts +24 -5
- package/src/plugins/builtin/earnings/history-model.ts +9 -1
- package/src/plugins/builtin/earnings/history-table.ts +16 -5
- package/src/plugins/builtin/earnings/history.tsx +40 -10
- package/src/plugins/builtin/earnings/index.tsx +3 -1
- package/src/plugins/builtin/earnings/no-earnings.ts +52 -0
- package/src/plugins/builtin/econ-statistics/detail.tsx +4 -1
- package/src/plugins/builtin/econ-statistics/headless.ts +5 -0
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +4 -0
- package/src/plugins/builtin/futures/model.ts +5 -4
- package/src/plugins/builtin/futures/series.ts +38 -1
- package/src/plugins/builtin/futures-curve/client.ts +4 -4
- package/src/plugins/builtin/futures-curve/headless.ts +33 -8
- package/src/plugins/builtin/futures-curve/index.tsx +4 -4
- package/src/plugins/builtin/futures-curve/model.ts +109 -6
- package/src/plugins/builtin/futures-curve/pane.tsx +24 -13
- package/src/plugins/builtin/insider/headless.ts +74 -4
- package/src/plugins/builtin/insider/index.tsx +99 -5
- package/src/plugins/builtin/insider/signals-client.ts +69 -0
- package/src/plugins/builtin/insider/signals-model.ts +146 -0
- package/src/plugins/builtin/insider/signals.tsx +176 -0
- package/src/plugins/builtin/iv-history/client.ts +41 -2
- package/src/plugins/builtin/iv-history/headless.ts +24 -16
- package/src/plugins/builtin/iv-history/screen-pane.tsx +25 -7
- package/src/plugins/builtin/jodi/client.ts +42 -205
- package/src/plugins/builtin/jodi/headless.ts +13 -4
- package/src/plugins/builtin/jodi/model.ts +18 -43
- package/src/plugins/builtin/jodi/pane.tsx +5 -0
- package/src/plugins/builtin/kelly-sizer/headless.ts +1 -1
- package/src/plugins/builtin/kelly-sizer/history.ts +5 -3
- package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
- package/src/plugins/builtin/macro-day/release-table.ts +3 -3
- package/src/plugins/builtin/market-movers/client.ts +32 -6
- package/src/plugins/builtin/market-movers/headless.ts +7 -2
- package/src/plugins/builtin/market-movers/model.ts +64 -3
- package/src/plugins/builtin/market-movers/screener.ts +20 -9
- package/src/plugins/builtin/market-valuation/detail.tsx +7 -2
- package/src/plugins/builtin/market-valuation/headless.ts +17 -4
- package/src/plugins/builtin/market-valuation/pane.tsx +7 -3
- package/src/plugins/builtin/market-valuation/view.ts +10 -0
- package/src/plugins/builtin/mna/model.ts +2 -1
- package/src/plugins/builtin/options-positioning/client.ts +10 -0
- package/src/plugins/builtin/options-positioning/headless.ts +88 -13
- package/src/plugins/builtin/options-positioning/index.tsx +2 -2
- package/src/plugins/builtin/options-positioning/model.ts +47 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +55 -26
- package/src/plugins/builtin/options-scenario/charts.tsx +13 -4
- package/src/plugins/builtin/options-scenario/client.ts +111 -17
- package/src/plugins/builtin/options-scenario/collar.ts +141 -0
- package/src/plugins/builtin/options-scenario/csp.ts +1 -1
- package/src/plugins/builtin/options-scenario/freshness.ts +27 -0
- package/src/plugins/builtin/options-scenario/headless.ts +28 -12
- package/src/plugins/builtin/options-scenario/index.tsx +1 -1
- package/src/plugins/builtin/options-scenario/model.ts +55 -5
- package/src/plugins/builtin/options-scenario/pane.tsx +24 -5
- package/src/plugins/builtin/perps/client.ts +77 -6
- package/src/plugins/builtin/perps/headless.ts +25 -14
- package/src/plugins/builtin/perps/index.tsx +1 -1
- package/src/plugins/builtin/perps/model.ts +32 -0
- package/src/plugins/builtin/perps/pane.tsx +5 -4
- package/src/plugins/builtin/portfolio-list/allocation.ts +9 -4
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +39 -20
- package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +15 -7
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +1 -1
- package/src/plugins/builtin/portfolio-list/column-values.ts +4 -3
- package/src/plugins/builtin/portfolio-list/command-bar.ts +1 -1
- package/src/plugins/builtin/portfolio-list/headless.ts +10 -3
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +36 -4
- package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +1 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +35 -6
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +2 -1
- package/src/plugins/builtin/portfolio-list/settings.ts +22 -0
- package/src/plugins/builtin/portfolio-list/since-last-seen.ts +197 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +14 -2
- package/src/plugins/builtin/rate-path/model.ts +1 -1
- package/src/plugins/builtin/rate-path/pane.tsx +2 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +14 -5
- package/src/plugins/builtin/research/event-model.ts +41 -49
- package/src/plugins/builtin/research/event-sources.ts +1 -1
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +6 -0
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
- package/src/plugins/builtin/reverse-dcf/headless.ts +25 -9
- package/src/plugins/builtin/reverse-dcf/index.tsx +4 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +39 -5
- package/src/plugins/builtin/reverse-dcf/pane.tsx +16 -6
- package/src/plugins/builtin/sectors/client.ts +46 -19
- package/src/plugins/builtin/sectors/sector-model.ts +22 -10
- package/src/plugins/builtin/shared/earnings-report.ts +48 -1
- package/src/plugins/builtin/shared/history-span.ts +55 -0
- package/src/plugins/builtin/shared/report-freshness.ts +20 -11
- package/src/plugins/builtin/shared/series-detail-chart.tsx +5 -2
- package/src/plugins/builtin/shared/series-list-detail.tsx +7 -3
- package/src/plugins/builtin/shared/ticker-surface.ts +16 -2
- package/src/plugins/builtin/shared/use-quote-board.ts +12 -5
- package/src/plugins/builtin/themes/headless.ts +15 -2
- package/src/plugins/builtin/thirteenf/api.ts +60 -43
- package/src/plugins/builtin/thirteenf/data.ts +76 -35
- package/src/plugins/builtin/thirteenf/headless.ts +2 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +10 -2
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +85 -17
- package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +92 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +26 -8
- package/src/plugins/builtin/ticker-detail/headless-schema.ts +4 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +95 -22
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +17 -3
- package/src/plugins/builtin/yield-curve/curves.ts +15 -1
- package/src/plugins/builtin/yield-curve/headless.ts +17 -7
- package/src/plugins/builtin/yield-curve/history.ts +2 -0
- package/src/plugins/builtin/yield-curve/index.tsx +23 -18
- package/src/plugins/builtin/yield-curve/series.ts +19 -9
- package/src/plugins/builtin/yield-curve/treasury-data.ts +2 -0
- package/src/plugins/builtin/yield-curve/world.ts +4 -0
- package/src/plugins/registry/host-actions.ts +3 -0
- package/src/{plugins/builtin/portfolio-list → portfolio}/mutations.ts +6 -6
- package/src/portfolio/valuation-basis.ts +4 -21
- package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
- package/src/remote/controller.ts +27 -7
- package/src/remote/schema.ts +25 -9
- package/src/renderers/browser/app-services.ts +7 -1
- package/src/renderers/browser/config-host.ts +55 -2
- package/src/renderers/browser/first-screen.ts +193 -0
- package/src/renderers/browser/main.tsx +59 -7
- package/src/renderers/cloudflare/http-proxy.ts +21 -13
- package/src/renderers/dom/desktop/controls.tsx +2 -0
- package/src/renderers/dom/desktop/query-bar.tsx +1 -1
- package/src/renderers/dom/dom-ui-host.tsx +2 -0
- package/src/renderers/dom/windowed-list.tsx +201 -0
- package/src/renderers/dom/windowed-rows.ts +65 -0
- package/src/renderers/electrobun/bun/application-menu/index.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +3 -7
- package/src/renderers/electrobun/bun/desktop/http-stream.ts +21 -6
- package/src/renderers/electrobun/bun/desktop/update.ts +132 -14
- package/src/renderers/electrobun/shared/protocol.ts +7 -4
- package/src/renderers/electrobun/view/http-fetch.ts +12 -1
- package/src/renderers/electrobun/view/update-host.ts +2 -2
- package/src/shares/bug-report-dialog.tsx +162 -0
- package/src/shares/bug-report-flow.tsx +84 -0
- package/src/shares/bug-report.ts +42 -0
- package/src/shares/capture-flow.ts +19 -0
- package/src/shares/capture-preview.tsx +129 -0
- package/src/shares/chat.ts +10 -1
- package/src/shares/dialog.tsx +16 -91
- package/src/shares/flow.tsx +12 -16
- package/src/shares/personal-pane.ts +35 -1
- package/src/sources/listing-symbols.ts +8 -4
- package/src/state/app/context/index.tsx +13 -0
- package/src/state/app/tab-cursor.ts +85 -0
- package/src/sync/core-contributors.ts +206 -59
- package/src/telemetry/usage-counts.ts +16 -0
- package/src/test-support/config-store.ts +31 -0
- package/src/test-support/moved-symbols.ts +64 -0
- package/src/theme/colors.ts +30 -0
- package/src/tickers/listing-fate.ts +242 -0
- package/src/tickers/open-target.ts +61 -11
- package/src/tickers/search/index.ts +45 -3
- package/src/tickers/search/ranking.ts +19 -2
- package/src/tickers/search/types.ts +3 -0
- package/src/tickers/search/upsert.ts +22 -7
- package/src/tickers/ticker-changes.ts +26 -9
- package/src/time-series/bar-spacing.ts +19 -0
- package/src/time-series/chart-data.ts +37 -13
- package/src/time-series/field-catalog.ts +5 -2
- package/src/time-series/forward-valuation.ts +10 -7
- package/src/time-series/fundamentals.ts +49 -11
- package/src/time-series/hooks.ts +2 -0
- package/src/time-series/live-bars.ts +3 -1
- package/src/time-series/parsed-history-cache.ts +2 -0
- package/src/time-series/price-units.ts +69 -0
- package/src/time-series/resolve.ts +71 -11
- package/src/time-series/session-bar.ts +138 -0
- package/src/time-series/spec.ts +20 -3
- package/src/time-series/studies.ts +211 -12
- package/src/time-series/transforms.ts +2 -0
- package/src/time-series/types.ts +11 -1
- package/src/time-series/valuation-price.ts +13 -0
- package/src/types/config.ts +24 -1
- package/src/types/financials.ts +2 -0
- package/src/types/headless.ts +27 -0
- package/src/types/plugin.ts +7 -0
- package/src/ui/host.tsx +5 -0
- package/src/updater/channel.ts +51 -0
- package/src/updater/github-releases.ts +85 -16
- package/src/updater/index.ts +128 -17
- package/src/utils/asset-category.ts +20 -0
- package/src/utils/datetime-format.ts +7 -1
- package/src/utils/decimal-comma.ts +83 -3
- package/src/utils/fundamentals.ts +7 -2
- package/src/utils/http-proxy-response.ts +66 -9
- package/src/utils/plugin-proxy-hosts.json +1 -5
- package/src/utils/semver.ts +47 -2
- package/src/utils/typo-distance.ts +22 -0
- package/src/plugins/builtin/shared/zip-text.ts +0 -46
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FINANCIAL_VINTAGE_NOTICE, statementHistoryNotes } from "../../../utils/financial-statements";
|
|
2
2
|
import { hasValidQuoteObservationTime } from "../../../market-data/quotes/freshness";
|
|
3
|
-
import { EXTENDED_SESSION_LABELS, getQuoteSessionFields, type ExtendedSession } from "../../../market-data/market/status";
|
|
3
|
+
import { EXTENDED_SESSION_LABELS, getQuoteSessionFields, quoteHeadlineTime, type ExtendedSession } from "../../../market-data/market/status";
|
|
4
4
|
import type { HeadlessPaneColumn, HeadlessPaneDefinition } from "../../../types/headless";
|
|
5
5
|
import type { TimeRange } from "../../../time-series/range";
|
|
6
6
|
import { formatNumber, formatPercentRaw } from "../../../utils/format";
|
|
@@ -29,12 +29,16 @@ import {
|
|
|
29
29
|
import {
|
|
30
30
|
loadHeadlessFinancials, loadHeadlessPriceHistory, loadHeadlessSymbols, resolveHeadlessInstrument,
|
|
31
31
|
} from "../shared/headless-market-data";
|
|
32
|
-
import { barHistoryFreshness,
|
|
32
|
+
import { barHistoryFreshness, quoteFreshnessFields, REPORTED_DATA } from "../shared/report-freshness";
|
|
33
33
|
import { cleanFloat32Price, historyPriceDecimals } from "../../../cli/history-rows";
|
|
34
34
|
import { historyCoverageNotice } from "../../../sources/history-coverage";
|
|
35
|
-
import { getChartResolutionLabel, isIntradayResolution, type ManualChartResolution } from "../../../time-series/resolution";
|
|
35
|
+
import { getChartResolutionLabel, isIntradayResolution, RANGE_HISTORY_RESOLUTION, type ManualChartResolution } from "../../../time-series/resolution";
|
|
36
|
+
import { DAY_SO_FAR_TEXT, withSessionBar } from "../../../time-series/session-bar";
|
|
36
37
|
import { buildReturnRows, closedMarketNote, type ReturnRange } from "./data-panes/returns-model";
|
|
37
38
|
import { loadReturnSeries } from "./data-panes/returns-history";
|
|
39
|
+
import {
|
|
40
|
+
formingHistoryBar, HISTORY_DEFAULT_RANGE, historyBarDate, historyBarSize, historyDateHeader, isDailyOrLongerBar, type HistoryBarSize,
|
|
41
|
+
} from "./data-panes/history-bars";
|
|
38
42
|
|
|
39
43
|
export const financialStatementsHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
40
44
|
...paneSchemas["financial-analysis-pane"],
|
|
@@ -247,7 +251,7 @@ export const quoteComparisonHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
247
251
|
...(quote.instrumentType ? { instrumentType: quote.instrumentType } : {}),
|
|
248
252
|
...(quote.priceBasis ? { priceBasis: quote.priceBasis } : {}),
|
|
249
253
|
change, changePercent, ...extended,
|
|
250
|
-
marketCap: quote.marketCap ?? null, updatedAt: quote
|
|
254
|
+
marketCap: quote.marketCap ?? null, updatedAt: quoteHeadlineTime(quote),
|
|
251
255
|
...quoteFreshnessFields(quote),
|
|
252
256
|
};
|
|
253
257
|
});
|
|
@@ -263,35 +267,86 @@ export const quoteComparisonHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
263
267
|
},
|
|
264
268
|
};
|
|
265
269
|
|
|
270
|
+
/** The price columns of HP, which `fn HP` prints and exports with the date column of the bars' size. */
|
|
271
|
+
const HISTORY_PRICE_COLUMNS: HeadlessPaneColumn[] = ["open", "high", "low", "close", "volume"].map((key) => ({
|
|
272
|
+
key, header: key, align: "right" as const,
|
|
273
|
+
format: (value: unknown) => value == null || !Number.isFinite(Number(value)) ? "-"
|
|
274
|
+
: key === "volume" ? formatNumber(Number(value), 0)
|
|
275
|
+
: formatPriceObservation(Number(value), { minimumFractionDigits: 2 }),
|
|
276
|
+
}));
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The columns of HP's rows. A day or longer bar is a date and an intraday one a
|
|
280
|
+
* UTC time. The text table dates a month's bar as the month and marks a bar
|
|
281
|
+
* whose period runs on (`2026-10 MTD`); the export keeps each bar's own date
|
|
282
|
+
* and adds a `Partial` column when one is.
|
|
283
|
+
*/
|
|
284
|
+
function historyColumns(size: HistoryBarSize, partial: boolean, text: boolean): HeadlessPaneColumn[] {
|
|
285
|
+
const daily = isDailyOrLongerBar(size);
|
|
286
|
+
const date: HeadlessPaneColumn = {
|
|
287
|
+
key: "date",
|
|
288
|
+
header: daily ? historyDateHeader(size) ?? "Date" : "Time (UTC)",
|
|
289
|
+
format: (value, row) => daily
|
|
290
|
+
? (text ? historyBarDate(String(value).slice(0, 10), size, row.partial === true) : String(value).slice(0, 10))
|
|
291
|
+
: String(value).slice(0, 16).replace("T", " "),
|
|
292
|
+
};
|
|
293
|
+
const marked: HeadlessPaneColumn[] = partial && !text
|
|
294
|
+
? [{ key: "partial", header: "Partial", format: (value) => value === true ? "true" : "" }]
|
|
295
|
+
: [];
|
|
296
|
+
return [date, ...HISTORY_PRICE_COLUMNS, ...marked];
|
|
297
|
+
}
|
|
298
|
+
|
|
266
299
|
export const historicalPricesHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
267
300
|
...paneSchemas["historical-prices-pane"],
|
|
268
301
|
shape: "rows",
|
|
269
|
-
description: "
|
|
270
|
-
describe: ({ symbols, options }) => `Historical Prices | ${symbols[0]} | ${options.range ??
|
|
271
|
-
columns:
|
|
272
|
-
{ key: "date", header: "Date", format: (value) => String(value).slice(0, 10) },
|
|
273
|
-
...["open", "high", "low", "close", "volume"].map((key) => ({
|
|
274
|
-
key, header: key, align: "right" as const,
|
|
275
|
-
format: (value: unknown) => value == null || !Number.isFinite(Number(value)) ? "-"
|
|
276
|
-
: key === "volume" ? formatNumber(Number(value), 0)
|
|
277
|
-
: formatPriceObservation(Number(value), { minimumFractionDigits: 2 }),
|
|
278
|
-
})),
|
|
279
|
-
],
|
|
302
|
+
description: "OHLCV bars (date, open, high, low, close, volume) for one ticker over a range: daily to start, weekly for 5Y and monthly for ALL. The newest bar is marked partial while its day, week or month runs on; a day's bar then comes from the live quote.",
|
|
303
|
+
describe: ({ symbols, options }) => `Historical Prices | ${symbols[0]} | ${options.range ?? HISTORY_DEFAULT_RANGE}`,
|
|
304
|
+
columns: historyColumns("1d", false, false),
|
|
280
305
|
async load({ symbols, options }, ctx) {
|
|
281
306
|
const symbol = symbols[0]!;
|
|
282
|
-
const range = (options.range ??
|
|
283
|
-
|
|
307
|
+
const range = (options.range ?? HISTORY_DEFAULT_RANGE) as TimeRange;
|
|
308
|
+
// A range of daily or longer bars reaches into the session now trading, whose bar is the quote's (withSessionBar).
|
|
309
|
+
const { symbol: listing, exchange } = await resolveHeadlessInstrument(ctx, symbol);
|
|
310
|
+
const [loaded, quote] = await Promise.all([
|
|
311
|
+
loadHeadlessPriceHistory(ctx, symbol, range),
|
|
312
|
+
isIntradayResolution(RANGE_HISTORY_RESOLUTION[range]) ? null : ctx.marketData.getQuote(listing, exchange ?? "").catch(() => null),
|
|
313
|
+
]);
|
|
314
|
+
// The venue whose sessions say whether a day, week or month is still running: the key's, or for a bare symbol its quote's.
|
|
315
|
+
const venue = exchange || quote?.listingExchangeName || quote?.exchangeName || undefined;
|
|
316
|
+
const now = Date.now();
|
|
317
|
+
const points = withSessionBar(loaded, quote, { now, venue });
|
|
284
318
|
const rows = points.map((point) => {
|
|
285
319
|
const date = new Date(point.date);
|
|
286
320
|
return {
|
|
287
321
|
date: Number.isFinite(date.getTime()) ? date.toISOString() : String(point.date),
|
|
288
322
|
...pricePointValues(point),
|
|
323
|
+
...(point.partial ? { partial: true } : {}),
|
|
289
324
|
};
|
|
290
325
|
}).sort((left, right) => left.date.localeCompare(right.date));
|
|
291
326
|
const notice = priceHistoryIntegrityNotice(rows.filter((row) => row.integrity).length);
|
|
327
|
+
const size = historyBarSize(rows.map((row) => Date.parse(row.date)));
|
|
328
|
+
// A weekly or monthly bar whose period runs on is dated by how far it runs, as `history` dates it.
|
|
329
|
+
const forming = formingHistoryBar(size, rows.at(-1)?.date, venue, now);
|
|
330
|
+
const shown = forming ? [...rows.slice(0, -1), { ...rows.at(-1)!, partial: true }] : rows;
|
|
331
|
+
const marked = shown.some((row) => row.partial === true);
|
|
332
|
+
// The day's bar built from the quote is not a close; a coin's day in progress, as its source flags it, keeps its line.
|
|
333
|
+
const dayToDate = points !== loaded && size === "1d" && shown.at(-1)?.partial === true;
|
|
292
334
|
// A bar history: dated by its last bar, stale once bars of its size stop arriving.
|
|
293
|
-
const freshness = {
|
|
294
|
-
|
|
335
|
+
const freshness = {
|
|
336
|
+
...barHistoryFreshness(isDailyOrLongerBar(size) ? size : null),
|
|
337
|
+
observedKey: "date", oldest: null,
|
|
338
|
+
...(forming ? { periodToDate: forming } : {}),
|
|
339
|
+
...(dayToDate ? { basis: "daily bars", note: `the newest bar is ${DAY_SO_FAR_TEXT}`, ...(quote ? { asOf: new Date(quote.lastUpdated).toISOString() } : {}) } : {}),
|
|
340
|
+
};
|
|
341
|
+
return {
|
|
342
|
+
rows: shown,
|
|
343
|
+
columns: historyColumns(size, marked, false),
|
|
344
|
+
textColumns: historyColumns(size, marked, true),
|
|
345
|
+
freshness,
|
|
346
|
+
...(notice ? { complete: false } : {}),
|
|
347
|
+
unavailableSymbols: rows.some((row) => row.close !== null) ? [] : [symbol],
|
|
348
|
+
metadata: { symbol, range, ...(size === "intraday" ? {} : { interval: size }), ...(notice ? { notices: [notice] } : {}) },
|
|
349
|
+
};
|
|
295
350
|
},
|
|
296
351
|
};
|
|
297
352
|
|
|
@@ -323,7 +378,7 @@ export const returnsHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
323
378
|
const percent = (value: number | null) => value === null ? null : value * 100;
|
|
324
379
|
const intraday = isIntradayResolution(series.resolution);
|
|
325
380
|
// A daily or longer bar is a trading date; an intraday one its UTC open.
|
|
326
|
-
const
|
|
381
|
+
const unmarked = returns.map((row) => ({
|
|
327
382
|
time: intraday ? new Date(row.timestamp).toISOString() : new Date(row.timestamp).toISOString().slice(0, 10),
|
|
328
383
|
close: rounded(row.price),
|
|
329
384
|
intervalChange: rounded(row.intervalChange),
|
|
@@ -332,15 +387,31 @@ export const returnsHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
332
387
|
totalReturnPct: percent(row.cumulativePercent),
|
|
333
388
|
...(row.inconsistent ? { inconsistent: true } : {}),
|
|
334
389
|
}));
|
|
390
|
+
// A weekly or monthly bar whose period runs on is the period so far: marked partial, and dated by how far it runs.
|
|
391
|
+
const forming = formingHistoryBar(series.resolution, unmarked.at(-1)?.time, series.venue || undefined, Date.now());
|
|
392
|
+
const rows = forming ? [...unmarked.slice(0, -1), { ...unmarked.at(-1)!, partial: true }] : unmarked;
|
|
335
393
|
const price = (value: unknown) => typeof value === "number" && Number.isFinite(value) ? value.toFixed(decimals) : "-";
|
|
336
|
-
const
|
|
337
|
-
{ key: "time", header: intraday ? "Time (UTC)" : "Date", format: (value) => String(value).slice(0, 16).replace("T", " ") },
|
|
394
|
+
const figures: HeadlessPaneColumn[] = [
|
|
338
395
|
{ key: "close", header: "Price", align: "right", format: price },
|
|
339
396
|
{ key: "intervalChange", header: "Int $", align: "right", format: price },
|
|
340
397
|
{ key: "intervalReturnPct", header: "Int %", align: "right", format: signedPercentCell },
|
|
341
398
|
{ key: "totalChange", header: "Total $", align: "right", format: price },
|
|
342
399
|
{ key: "totalReturnPct", header: "Total %", align: "right", format: signedPercentCell },
|
|
343
400
|
];
|
|
401
|
+
// The text table dates a month's bar as the month and marks one still running (`2026-10 MTD`); the export keeps the day and says which bar is partial.
|
|
402
|
+
const timeColumn = (text: boolean): HeadlessPaneColumn => ({
|
|
403
|
+
key: "time",
|
|
404
|
+
header: intraday ? "Time (UTC)" : "Date",
|
|
405
|
+
format: (value, row) => text && !intraday
|
|
406
|
+
? historyBarDate(String(value).slice(0, 10), series.resolution, row.partial === true)
|
|
407
|
+
: String(value).slice(0, 16).replace("T", " "),
|
|
408
|
+
});
|
|
409
|
+
const columns: HeadlessPaneColumn[] = [
|
|
410
|
+
timeColumn(false),
|
|
411
|
+
...figures,
|
|
412
|
+
...forming ? [{ key: "partial", header: "Partial", format: (value: unknown) => value === true ? "true" : "" }] : [],
|
|
413
|
+
];
|
|
414
|
+
const textColumns: HeadlessPaneColumn[] = [timeColumn(true), ...figures];
|
|
344
415
|
// An interval the range is too long for is served in the nearest longer bar; say so rather than switch silently.
|
|
345
416
|
const notices = [
|
|
346
417
|
series.resolution !== requested
|
|
@@ -352,12 +423,14 @@ export const returnsHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
352
423
|
return {
|
|
353
424
|
rows,
|
|
354
425
|
columns,
|
|
426
|
+
textColumns,
|
|
355
427
|
unavailableSymbols: rows.length ? [] : [key],
|
|
356
428
|
...(rows.length ? {} : { errors: [`${key}: No bars in this window.`] }),
|
|
357
429
|
freshness: {
|
|
358
430
|
...barHistoryFreshness(intraday ? null : series.resolution),
|
|
359
431
|
observedKey: "time",
|
|
360
432
|
oldest: null,
|
|
433
|
+
...(forming ? { periodToDate: forming } : {}),
|
|
361
434
|
...(series.venue ? { cadenceExchange: series.venue } : {}),
|
|
362
435
|
// The window ends at the last session while the market is shut, which the status line says.
|
|
363
436
|
...(series.lastSession ? { note: closedMarketNote(series.lastSession, series.visibleTo) } : {}),
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
} from "../../../../market-data/market/format";
|
|
21
21
|
import type { OverviewFunctionLink, PositionTableRow, StatField } from "./types";
|
|
22
22
|
import { getPortfolioPositionMetrics, getPortfolioQuoteDisplay, resolvePortfolioMarketValue, resolvePortfolioPositionPnl, portfolioPnlPercent, signedPositionDirection } from "../../../../portfolio/position-metrics";
|
|
23
|
-
import { liveDividendYield, liveForwardPE, liveMarketCapitalization, liveTrailingPE } from "
|
|
23
|
+
import { liveDividendYield, liveForwardPE, liveMarketCapitalization, liveTrailingPE } from "../../../../market-data/quotes/live-valuation";
|
|
24
24
|
import { atRegularSessionPrice } from "../../../../market-data/quotes/regular-session-valuation";
|
|
25
25
|
import { formatReportedMoney } from "../../../../utils/reported-money";
|
|
26
26
|
import { enterpriseValueCheck, fundamentalsCurrency, fundamentalsEpsCurrency, reportedEnterpriseValue } from "../../../../utils/fundamentals";
|
|
@@ -44,7 +44,7 @@ import { useFuturesContractTerms } from "./overview/futures-contract";
|
|
|
44
44
|
import { buildOverviewStats, buildPositionRows, buildProfileFields } from "./overview/model";
|
|
45
45
|
import type { OverviewFunctionLink, StatField } from "./overview/types";
|
|
46
46
|
import { describeFundamentalMarketCap } from "../../../utils/market-capitalization";
|
|
47
|
-
import { liveFiftyTwoWeekRange, liveMarketCapitalization } from "
|
|
47
|
+
import { liveFiftyTwoWeekRange, liveMarketCapitalization } from "../../../market-data/quotes/live-valuation";
|
|
48
48
|
import { instrumentTypeLabel } from "../../../tickers/instrument-kind";
|
|
49
49
|
import { ClippedText } from "../../../components/ui/display";
|
|
50
50
|
|
|
@@ -78,6 +78,21 @@ function layerColumns(layer: GeoLayerInfo): HeadlessPaneColumn[] {
|
|
|
78
78
|
];
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* What a search that finds nothing says. A search outside a one-country layer
|
|
83
|
+
* finds nothing because the layer has nothing there; ships are found by name,
|
|
84
|
+
* not by the ticker of the company that runs them, so a ticker that links no
|
|
85
|
+
* fleet (or a server that links none) gets a name to try.
|
|
86
|
+
*/
|
|
87
|
+
function emptySearchNote(layer: GeoLayerInfo, query: string): string | null {
|
|
88
|
+
const typed = query.trim();
|
|
89
|
+
if (!typed) return null;
|
|
90
|
+
const coverage = layerCoverageNote(layer);
|
|
91
|
+
if (coverage) return `No ${layer.name.toLowerCase()} match "${typed}". ${coverage}`;
|
|
92
|
+
if (layer.id === "vessels") return `No ${layer.name.toLowerCase()} match "${typed}"; try a ship-name prefix, such as --query Norwegian.`;
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
81
96
|
export const mapHeadless = {
|
|
82
97
|
shape: "rows",
|
|
83
98
|
description: "Trading venues with open status and local time, or one map layer's entities (ships, chokepoints, ports, airports, pipelines, fields, terminals) with their fields and linked tickers.",
|
|
@@ -126,12 +141,11 @@ export const mapHeadless = {
|
|
|
126
141
|
lon: row.lon,
|
|
127
142
|
lat: row.lat,
|
|
128
143
|
}));
|
|
129
|
-
const
|
|
144
|
+
const empty = emptySearchNote(layer, query);
|
|
130
145
|
return {
|
|
131
146
|
columns: layerColumns({ ...layer, columns }),
|
|
132
147
|
rows,
|
|
133
|
-
|
|
134
|
-
...(query.trim() && coverage ? { empty: `No ${layer.name.toLowerCase()} match "${query.trim()}". ${coverage}` } : {}),
|
|
148
|
+
...(empty ? { empty } : {}),
|
|
135
149
|
freshness: layerFreshness(layer),
|
|
136
150
|
complete: layer.status === "ok",
|
|
137
151
|
errors: layer.status === "unavailable" ? [`${layer.name}: ${layer.statusNote ?? "unavailable"}`] : [],
|
|
@@ -73,6 +73,19 @@ export interface CurveData {
|
|
|
73
73
|
lookbacks: YieldLookbackCurves | null;
|
|
74
74
|
/** Null for the FRED fallback: its spreads follow from the points and look-backs. */
|
|
75
75
|
spreads: CurveSpreadFigure[] | null;
|
|
76
|
+
/**
|
|
77
|
+
* FRED `info.source` when these points are the constant-maturity fallback.
|
|
78
|
+
* Null when that fallback came back without series info. Absent on a stored curve.
|
|
79
|
+
*/
|
|
80
|
+
seriesSource?: string | null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function fredSeriesSource(points: readonly YieldPoint[]): string | null {
|
|
84
|
+
const sources = [...new Set(points.flatMap((point) => {
|
|
85
|
+
const source = point.source?.trim();
|
|
86
|
+
return source ? [source] : [];
|
|
87
|
+
}))];
|
|
88
|
+
return sources.length ? sources.join(", ") : null;
|
|
76
89
|
}
|
|
77
90
|
|
|
78
91
|
type CurveClient = Pick<typeof apiClient, "getCloudCurve" | "getCloudYieldCurve" | "getCloudFredSeries">;
|
|
@@ -105,7 +118,8 @@ async function loadFredCurve(requestedDate: string, client: CurveClient): Promis
|
|
|
105
118
|
const points = completeYieldCurve(requestedDate
|
|
106
119
|
? await loadHistoricalYieldCurve(requestedDate, (id, options) => client.getCloudFredSeries(id, options))
|
|
107
120
|
: await loadYieldCurve(() => client.getCloudYieldCurve()));
|
|
108
|
-
return { curve: "us", requestedDate, basis: "par", couponsPerYear: 2, points, lookbacks: null, spreads: null
|
|
121
|
+
return { curve: "us", requestedDate, basis: "par", couponsPerYear: 2, points, lookbacks: null, spreads: null,
|
|
122
|
+
seriesSource: fredSeriesSource(points) };
|
|
109
123
|
}
|
|
110
124
|
|
|
111
125
|
/** The spreads GC shows: the stored curve's, or for FRED's curve its own with no percentile. */
|
|
@@ -153,20 +153,31 @@ export function createYieldCurveHeadless(
|
|
|
153
153
|
const compareAsOf = comparePoints ? curveAsOf(comparePoints) : null;
|
|
154
154
|
const compareAt = (maturity: string) => comparePoints?.find((point) => point.maturity === maturity)?.yield ?? null;
|
|
155
155
|
// The stored curve brings its look-backs; the FRED fallback's load after it, and a failure leaves the move blank.
|
|
156
|
-
const lookbacks = data.lookbacks ?? (session ? await loadLookbacks(session, context).catch(() => ({})) : {});
|
|
156
|
+
const lookbacks: YieldLookbackCurves = data.lookbacks ?? (session ? await loadLookbacks(session, context).catch(() => ({})) : {});
|
|
157
157
|
const change1d = new Map(yieldTenorRows(points, lookbacks).map((row) => [row.id, row.change1d]));
|
|
158
158
|
const rows = [...points].sort((a, b) => a.maturityYears - b.maturityYears);
|
|
159
159
|
const missing = points.filter((point) => point.yield == null);
|
|
160
160
|
const missingTenors = missing.map((point) => point.maturity);
|
|
161
|
-
//
|
|
162
|
-
const
|
|
163
|
-
|
|
161
|
+
// A blank on a past date is not evidence the tenor was unissued. Leave the cell empty.
|
|
162
|
+
const pastBlanks = requestedDate && missing.length < points.length
|
|
163
|
+
? missing.filter((point) => !point.error).map((point) => point.maturity) : [];
|
|
164
|
+
const unavailable = missingTenors.filter((maturity) => !pastBlanks.includes(maturity));
|
|
164
165
|
const spreads = curveSpreadFigures(data, lookbacks);
|
|
165
166
|
const spread = (id: string) => bp(spreads.find((entry) => entry.id === id)?.value ?? null);
|
|
166
167
|
const twosTens = spread("2s10s");
|
|
168
|
+
const fredNames = new Set<string>();
|
|
169
|
+
for (const curvePoints of [points, ...Object.values(lookbacks)]) {
|
|
170
|
+
for (const point of curvePoints ?? []) {
|
|
171
|
+
const source = point.source?.trim();
|
|
172
|
+
if (source) fredNames.add(source);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const seriesSource = fredNames.size > 0 ? [...fredNames].join(", ")
|
|
176
|
+
: data.seriesSource ? data.seriesSource
|
|
177
|
+
: data.seriesSource === null ? "FRED" : CURVE_PUBLISHERS[curve];
|
|
167
178
|
return {
|
|
168
|
-
//
|
|
169
|
-
freshness: { source:
|
|
179
|
+
// A stored curve cites its publisher. FRED's fallback cites info.source, not that publisher.
|
|
180
|
+
freshness: { source: seriesSource, status: "not-a-feed", tradingDayMarket: CURVE_MARKETS[curve],
|
|
170
181
|
...(requestedDate ? { basis: "historical curve" } : { basis: "daily curve", cadence: "daily" }) },
|
|
171
182
|
...(comparePoints ? { columns: compareColumns(compareAsOf) } : {}),
|
|
172
183
|
rows: rows.map((point) => {
|
|
@@ -180,7 +191,6 @@ export function createYieldCurveHeadless(
|
|
|
180
191
|
: {}),
|
|
181
192
|
};
|
|
182
193
|
}),
|
|
183
|
-
...(notIssued.length ? { notes: [`Not issued then: ${notIssued.join(", ")}`] } : {}),
|
|
184
194
|
errors: [
|
|
185
195
|
...yieldCurveErrors(points),
|
|
186
196
|
...(unavailable.length ? [`Tenors unavailable: ${unavailable.join(", ")}`] : []),
|
|
@@ -88,11 +88,13 @@ function curveOnOrBefore(results: readonly PromiseSettledResult<TreasurySeriesRe
|
|
|
88
88
|
const result = results[index]!;
|
|
89
89
|
const data = result.status === "fulfilled" ? result.value : null;
|
|
90
90
|
const point = data?.observations.find((entry) => entry.date === asOf);
|
|
91
|
+
const source = data?.payload.info?.source?.trim();
|
|
91
92
|
return {
|
|
92
93
|
maturity,
|
|
93
94
|
maturityYears: years,
|
|
94
95
|
yield: point?.value ?? null,
|
|
95
96
|
asOf: point ? asOf : null,
|
|
97
|
+
...(source ? { source } : {}),
|
|
96
98
|
stale: data?.payload.stale ?? false,
|
|
97
99
|
fetchedAt: data?.payload.fetchedAt,
|
|
98
100
|
error: result.status === "rejected"
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
compareDate, curveIdOf, CURVE_OPTIONS, curveSpreadFigures, loadComparePoints, loadCurveData, parseCompareInput,
|
|
31
31
|
parseCurveArgument, type CurveId,
|
|
32
32
|
} from "./curves";
|
|
33
|
-
import { forwardCurve } from "./forward";
|
|
33
|
+
import { forwardCurve, type CurveBasis } from "./forward";
|
|
34
34
|
import { loadYieldCurveLookbacks, yieldCurveDate } from "./history";
|
|
35
35
|
import { curveAsOf, isYieldObservationDate, type YieldPoint, yieldCurveErrors } from "./treasury-data";
|
|
36
36
|
import { loadWorldRows, type WorldRow } from "./world";
|
|
@@ -38,15 +38,17 @@ import { loadWorldRows, type WorldRow } from "./world";
|
|
|
38
38
|
type GcTab = "curve" | "world";
|
|
39
39
|
const TABS = [{ label: "Curve", value: "curve" }, { label: "World", value: "world" }];
|
|
40
40
|
const EMPTY_POINTS: YieldPoint[] = [];
|
|
41
|
-
/**
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
/** Axis word for the loaded curve's basis. */
|
|
42
|
+
const BASIS_LABEL: Record<CurveBasis, string> = {
|
|
43
|
+
par: "Par yield",
|
|
44
|
+
zero: "Zero rate",
|
|
45
|
+
benchmark: "Benchmark yield",
|
|
46
|
+
spread: "Spread",
|
|
46
47
|
};
|
|
47
|
-
const
|
|
48
|
-
/**
|
|
49
|
-
const
|
|
48
|
+
const basisText = (basis: CurveBasis | undefined): string | null => basis ? BASIS_LABEL[basis] : null;
|
|
49
|
+
/** TIPS par yields are real yields, and the breakeven curve is the spread readers call a breakeven. */
|
|
50
|
+
const CURVE_LEVEL: Partial<Record<CurveId, string>> = { "us-real": "Real par yield", "us-breakeven": "Breakeven" };
|
|
51
|
+
const curveLevel = (curve: CurveId, basis: CurveBasis | undefined): string | null => CURVE_LEVEL[curve] ?? basisText(basis);
|
|
50
52
|
const TENOR: DataTableColumn = { id: "tenor", label: "Tenor", width: 7, align: "left" };
|
|
51
53
|
const YIELD: DataTableColumn = { id: "yield", label: "Yield", width: 8, align: "right" };
|
|
52
54
|
const CHANGE_1D: DataTableColumn = { id: "change1d", label: "1D chg", width: 8, align: "right" };
|
|
@@ -268,29 +270,31 @@ function CurveTab({ curve, onCurveChange, focused, width, height }: {
|
|
|
268
270
|
useChartTableSelection({ rows, getId: tenorKey, getDate: tenorPosition, selectedId, onSelect: setSelectedTenor,
|
|
269
271
|
focused: focused && !editing });
|
|
270
272
|
const compareName = curveLookbackLabel(compareLabel);
|
|
273
|
+
const level = curveLevel(curve, current?.basis);
|
|
274
|
+
const levelName = level ?? "Yield";
|
|
271
275
|
const series = useMemo(() => differenceView
|
|
272
276
|
? [yieldDifferenceSeries(rows, compareName, { positive: colors.positive, negative: colors.negative })]
|
|
273
277
|
: yieldCurveChartSeries(points, lookbackCurves,
|
|
274
278
|
{ current: colors.positive, ghosts: curveGhostColors(colors), compare: colors.warning, forward: colors.borderFocused },
|
|
275
|
-
{ compare: comparing ? { label: compareLabel, points: comparePoints ?? [] } : null, forward, label:
|
|
276
|
-
[colors, compareLabel, compareName, comparePoints, comparing,
|
|
279
|
+
{ compare: comparing ? { label: compareLabel, points: comparePoints ?? [] } : null, forward, label: levelName }),
|
|
280
|
+
[colors, compareLabel, compareName, comparePoints, comparing, differenceView, forward, levelName, lookbackCurves, points, rows]);
|
|
277
281
|
// Mixed tenor dates still draw the curve: the As of column and the footer
|
|
278
282
|
// warning say which tenors differ.
|
|
279
283
|
const mixedDates = new Set(rows.flatMap((row) => row.yield == null ? [] : [row.asOf])).size > 1;
|
|
280
284
|
const columns = useMemo<DataTableColumn[]>(() => [
|
|
281
|
-
TENOR,
|
|
285
|
+
TENOR, { ...YIELD, label: levelName, width: Math.max(8, levelName.length) },
|
|
282
286
|
...(comparing
|
|
283
287
|
? [{ id: "compare", label: compareName || "vs", width: Math.max(8, compareName.length + 1), align: "right" as const },
|
|
284
288
|
{ id: "changeCompare", label: "Chg", width: 8, align: "right" as const }, CHANGE_1D]
|
|
285
289
|
: LOOKBACK_COLUMNS),
|
|
286
290
|
...(forward.length ? [FORWARD] : []),
|
|
287
291
|
...(mixedDates ? [AS_OF_COLUMN] : []),
|
|
288
|
-
], [compareName, comparing,
|
|
289
|
-
const caption = differenceView ? `Change vs ${compareName}` :
|
|
292
|
+
], [compareName, comparing, forward.length, levelName, mixedDates]);
|
|
293
|
+
const caption = differenceView ? `Change vs ${compareName}` : level ? `${level} % by maturity` : "Yield % by maturity";
|
|
290
294
|
const formatValue = differenceView ? formatBp : formatYield;
|
|
291
295
|
// The strip's label shares its row with the curve's shape and the point. A
|
|
292
296
|
// curve with fewer than two yields has neither, so the tenors take the band.
|
|
293
|
-
const strip = curveStrip(series, formatValue, { caption: differenceView ? "Change" : "Yield %", selectedPointId: selectedId });
|
|
297
|
+
const strip = curveStrip(series, formatValue, { caption: differenceView ? "Change" : level ? `${level} %` : "Yield %", selectedPointId: selectedId });
|
|
294
298
|
const renderCell = useCallback((row: YieldTenorRow, column: DataTableColumn) => {
|
|
295
299
|
if (column.id === "tenor") return { text: row.id };
|
|
296
300
|
if (column.id === "asOf") return { text: row.asOf ?? "--", color: colors.textMuted };
|
|
@@ -403,7 +407,8 @@ function WorldTab({ onOpen, focused, width, height }: {
|
|
|
403
407
|
points: row.points.filter((point) => point.years > 0)
|
|
404
408
|
.map((point) => ({ id: point.tenor, label: point.tenor, x: point.years, value: point.yield })),
|
|
405
409
|
})), [rows, selectedId]);
|
|
406
|
-
const
|
|
410
|
+
const level = basisText(rows.find((row) => row.id === selectedId)?.basis);
|
|
411
|
+
const strip = curveStrip(series, formatYield, { caption: level ? `${level} %` : "Yield %" });
|
|
407
412
|
const renderCell = useCallback((row: WorldRow, column: DataTableColumn) => {
|
|
408
413
|
if (column.id === "market") return { text: row.market };
|
|
409
414
|
if (column.id === "asOf") return { text: row.asOf, color: colors.textMuted };
|
|
@@ -428,9 +433,9 @@ function WorldTab({ onOpen, focused, width, height }: {
|
|
|
428
433
|
rootBefore={<ChartTableHeader width={width} height={height} tableRows={rows.length} tableColumns={WORLD_COLUMNS}
|
|
429
434
|
chart={strip ? {
|
|
430
435
|
render: (size) => <CurveSurface series={series} width={size.width} height={size.height} display="chart"
|
|
431
|
-
caption={
|
|
436
|
+
caption={level ? `${level} % by maturity` : "Yield % by maturity"} xScale="log" formatValue={formatYield} formatChange={formatYieldChange}
|
|
432
437
|
formatAxisValue={formatPercentAxis} primarySeriesId={selectedId ?? undefined} />,
|
|
433
|
-
minRows: curveSurfaceMinRows({ series, width, caption:
|
|
438
|
+
minRows: curveSurfaceMinRows({ series, width, caption: level ? `${level} % by maturity` : "Yield % by maturity" }),
|
|
434
439
|
strip,
|
|
435
440
|
} : null} />}
|
|
436
441
|
/>
|
|
@@ -5,20 +5,26 @@ import type { SeriesDefinition } from "../../../series/types";
|
|
|
5
5
|
import { unavailableOnServer } from "../shared/cloud-resource";
|
|
6
6
|
import { TREASURY_MATURITIES } from "./treasury-data";
|
|
7
7
|
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* FRED constant-maturity yields, charted as `UST:10Y` (DGS10). These ids stay
|
|
10
|
+
* on that series. The curve pane plots the Treasury par curve, below.
|
|
11
|
+
*/
|
|
9
12
|
export const TREASURY_SERIES: readonly SeriesDefinition[] = TREASURY_MATURITIES.map((entry) => ({
|
|
10
13
|
id: `ust:${entry.maturity}`,
|
|
11
|
-
name: `${entry.maturity} Treasury
|
|
14
|
+
name: `${entry.maturity} Treasury constant maturity`,
|
|
15
|
+
description: `FRED ${entry.seriesId}, the constant-maturity yield, not the Treasury par curve.`,
|
|
12
16
|
unit: "%",
|
|
13
17
|
frequency: "daily",
|
|
14
18
|
group: "Rates",
|
|
15
|
-
tags: ["treasury", "yield curve", entry.seriesId],
|
|
16
|
-
publisher: "
|
|
19
|
+
tags: ["treasury", "yield curve", "constant maturity", entry.seriesId],
|
|
20
|
+
publisher: "U.S. Treasury",
|
|
17
21
|
url: fredSeriesUrl(entry.seriesId),
|
|
18
22
|
expression: `UST:${entry.maturity}`,
|
|
19
23
|
}));
|
|
20
24
|
|
|
21
25
|
/** The government curves stored beside the Treasury's, with the tenors each publisher prints. */
|
|
26
|
+
const BILL_TENORS = new Set(["1M", "3M", "6M", "1Y"]);
|
|
27
|
+
|
|
22
28
|
const CURVES: ReadonlyArray<{
|
|
23
29
|
curve: CloudCurveId;
|
|
24
30
|
name: (tenor: string) => string;
|
|
@@ -26,6 +32,11 @@ const CURVES: ReadonlyArray<{
|
|
|
26
32
|
tags: readonly string[];
|
|
27
33
|
tenors: readonly string[];
|
|
28
34
|
}> = [
|
|
35
|
+
{
|
|
36
|
+
curve: "us", publisher: "U.S. Treasury", tags: ["Treasury", "par yield", "UST"],
|
|
37
|
+
name: (tenor) => `US Treasury ${tenor} par yield`,
|
|
38
|
+
tenors: ["1M", "6W", "2M", "3M", "4M", "6M", "1Y", "2Y", "3Y", "5Y", "7Y", "10Y", "20Y", "30Y"],
|
|
39
|
+
},
|
|
29
40
|
{
|
|
30
41
|
curve: "de", publisher: "Bundesbank", tags: ["Germany", "Bund", "euro"],
|
|
31
42
|
name: (tenor) => `Germany ${tenor} Bund yield`,
|
|
@@ -42,8 +53,8 @@ const CURVES: ReadonlyArray<{
|
|
|
42
53
|
tenors: ["1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "15Y", "20Y", "25Y", "30Y", "40Y"],
|
|
43
54
|
},
|
|
44
55
|
{
|
|
45
|
-
curve: "ca", publisher: "Bank of Canada", tags: ["Canada", "benchmark"],
|
|
46
|
-
name: (tenor) => `Canada ${tenor} benchmark yield`,
|
|
56
|
+
curve: "ca", publisher: "Bank of Canada", tags: ["Canada", "benchmark", "bill"],
|
|
57
|
+
name: (tenor) => BILL_TENORS.has(tenor) ? `Canada ${tenor} Treasury bill yield` : `Canada ${tenor} benchmark yield`,
|
|
47
58
|
tenors: ["1M", "3M", "6M", "1Y", "2Y", "3Y", "5Y", "7Y", "10Y", "30Y"],
|
|
48
59
|
},
|
|
49
60
|
{
|
|
@@ -52,15 +63,14 @@ const CURVES: ReadonlyArray<{
|
|
|
52
63
|
tenors: ["3M", "6M", "1Y", "2Y", "3Y", "5Y", "7Y", "10Y", "15Y", "20Y", "30Y"],
|
|
53
64
|
},
|
|
54
65
|
{
|
|
55
|
-
// FRED lists the 5Y and 10Y; the curve has the rest.
|
|
56
66
|
curve: "us-real", publisher: "U.S. Treasury", tags: ["TIPS", "real yield", "treasury"],
|
|
57
67
|
name: (tenor) => `${tenor} TIPS real yield`,
|
|
58
|
-
tenors: ["7Y", "20Y", "30Y"],
|
|
68
|
+
tenors: ["5Y", "7Y", "10Y", "20Y", "30Y"],
|
|
59
69
|
},
|
|
60
70
|
{
|
|
61
71
|
curve: "us-breakeven", publisher: "U.S. Treasury", tags: ["breakeven", "inflation expectations", "treasury"],
|
|
62
72
|
name: (tenor) => `${tenor} breakeven inflation (nominal less TIPS)`,
|
|
63
|
-
tenors: ["7Y", "20Y", "30Y"],
|
|
73
|
+
tenors: ["5Y", "7Y", "10Y", "20Y", "30Y"],
|
|
64
74
|
},
|
|
65
75
|
];
|
|
66
76
|
|
|
@@ -4,6 +4,8 @@ export interface YieldPoint {
|
|
|
4
4
|
maturity: string; // "1M", "3M", "6M", "1Y", "2Y", "3Y", "5Y", "7Y", "10Y", "20Y", "30Y"
|
|
5
5
|
maturityYears: number; // 0.083, 0.25, 0.5, 1, 2, 3, 5, 7, 10, 20, 30
|
|
6
6
|
yield: number | null; // percent, e.g., 4.29
|
|
7
|
+
/** FRED `info.source` when this point came from a FRED series. Absent on a stored curve. */
|
|
8
|
+
source?: string;
|
|
7
9
|
asOf?: string | null; // FRED observation date, absent on older servers
|
|
8
10
|
stale?: boolean;
|
|
9
11
|
fetchedAt?: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { apiClient } from "../../../api-client";
|
|
2
2
|
import type { CloudCurvePoint, CloudWorldCurves } from "../../../api-client/yield-curves";
|
|
3
3
|
import { isCurveId, type CurveId } from "./curves";
|
|
4
|
+
import type { CurveBasis } from "./forward";
|
|
4
5
|
|
|
5
6
|
export interface WorldRow {
|
|
6
7
|
id: CurveId;
|
|
@@ -17,6 +18,8 @@ export interface WorldRow {
|
|
|
17
18
|
thirtyYearChange: number | null;
|
|
18
19
|
/** 10Y minus 2Y. */
|
|
19
20
|
twosTens: number | null;
|
|
21
|
+
/** The basis the publisher printed: par, zero, benchmark, or spread. */
|
|
22
|
+
basis: CurveBasis;
|
|
20
23
|
twosTensChange: number | null;
|
|
21
24
|
/** The whole latest curve, for the chart. */
|
|
22
25
|
points: CloudCurvePoint[];
|
|
@@ -43,6 +46,7 @@ function worldRows(payload: CloudWorldCurves): WorldRow[] {
|
|
|
43
46
|
twoYearChange: minus(now("2Y"), before("2Y")), tenYearChange: minus(now("10Y"), before("10Y")),
|
|
44
47
|
thirtyYearChange: minus(now("30Y"), before("30Y")),
|
|
45
48
|
twosTens, twosTensChange: minus(twosTens, minus(before("10Y"), before("2Y"))),
|
|
49
|
+
basis: entry.curve.basis,
|
|
46
50
|
points: entry.points,
|
|
47
51
|
}];
|
|
48
52
|
});
|
|
@@ -44,6 +44,8 @@ export interface PluginHostActions {
|
|
|
44
44
|
/** `options.fieldKey` starts the cursor on that setting, as a setting picked in the command bar does. */
|
|
45
45
|
openPaneSettings(paneId?: string, options?: { fieldKey?: string }): void;
|
|
46
46
|
sharePane(paneId?: string): void;
|
|
47
|
+
/** Report a Bug for a pane (the focused one when omitted): its picture and a line, posted to #bugs. Not on the plugin API. */
|
|
48
|
+
reportPaneBug(paneId?: string): void;
|
|
47
49
|
/**
|
|
48
50
|
* A picture of a pane (the focused one when omitted) as it looks now, its
|
|
49
51
|
* footer reading as a dated status line. Not on the plugin API.
|
|
@@ -114,6 +116,7 @@ export function createDefaultHostActions(current?: () => Pick<PluginHostActions,
|
|
|
114
116
|
openBuiltInWorkflow: () => {},
|
|
115
117
|
openPaneSettings: () => {},
|
|
116
118
|
sharePane: () => {},
|
|
119
|
+
reportPaneBug: () => {},
|
|
117
120
|
capturePaneImage: async () => { throw new Error("Pictures of panes are not available here."); },
|
|
118
121
|
promptDialog: async () => undefined,
|
|
119
122
|
openWindowMode: () => {},
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { AppConfig } from "
|
|
2
|
-
import type { Portfolio, PortfolioCash, TickerPosition, TickerRecord, Watchlist } from "
|
|
3
|
-
import { resolveCurrencyUnit } from "
|
|
4
|
-
import { slugifyName } from "
|
|
5
|
-
import { parseCollectionRef } from "
|
|
6
|
-
import { findNamedCollection } from "
|
|
1
|
+
import type { AppConfig } from "../types/config";
|
|
2
|
+
import type { Portfolio, PortfolioCash, TickerPosition, TickerRecord, Watchlist } from "../types/ticker";
|
|
3
|
+
import { resolveCurrencyUnit } from "../utils/currency-units";
|
|
4
|
+
import { slugifyName } from "../utils/slugify";
|
|
5
|
+
import { parseCollectionRef } from "../team/collections";
|
|
6
|
+
import { findNamedCollection } from "./collection-name";
|
|
7
7
|
|
|
8
8
|
export interface DeleteWatchlistResult {
|
|
9
9
|
config: AppConfig;
|