gloomberb 0.18.1 → 0.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +106 -5
- package/src/api-client/commodity-balances.ts +6 -0
- package/src/api-client/company-kpis.ts +9 -0
- package/src/api-client/crypto-markets.ts +9 -0
- package/src/api-client/data.ts +21 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/index.ts +2 -0
- package/src/api-client/request.ts +5 -3
- package/src/api-client/telemetry.ts +17 -0
- package/src/api-client/types.ts +5 -0
- package/src/app/global-shortcuts.ts +1 -16
- package/src/app/pane-runtime/plugin-bindings.ts +3 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
- package/src/app/runtime/plugin-bindings.ts +6 -2
- package/src/app/runtime/update.ts +28 -8
- package/src/architecture/pane-tabs-allowlist.json +65 -0
- package/src/brokers/logos.ts +66 -0
- package/src/brokers/profile-form.ts +3 -0
- package/src/brokers/remote-broker-adapter.ts +13 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/service-capabilities.ts +7 -1
- package/src/cli/base-converter.ts +60 -18
- package/src/cli/commands/market.ts +354 -187
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +167 -60
- package/src/cli/currency-hints.ts +27 -0
- package/src/cli/fx-pair.ts +41 -4
- package/src/cli/glossary.ts +57 -4
- package/src/cli/help.ts +1 -1
- package/src/cli/helpers.ts +36 -3
- package/src/cli/index.ts +12 -1
- package/src/cli/issuer-membership.ts +103 -0
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +51 -3
- package/src/cli/name-entries.ts +51 -12
- package/src/cli/pane-functions/capabilities.ts +2 -0
- package/src/cli/pane-functions/catalog.ts +19 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +11 -5
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +27 -3
- package/src/cli/pane-functions/headless.ts +31 -11
- package/src/cli/pane-functions/index.ts +67 -7
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +21 -6
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/report-tables.ts +136 -18
- package/src/cli/result.ts +12 -4
- package/src/cli/series-search.ts +2 -1
- package/src/cli/symbol-list.ts +47 -11
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +25 -7
- package/src/components/chart/composite/drawing-store.ts +26 -6
- package/src/components/chart/composite/format.ts +13 -1
- package/src/components/chart/composite/interactions.ts +2 -1
- package/src/components/chart/composite/level-labels.ts +30 -0
- package/src/components/chart/composite/levels.ts +17 -6
- package/src/components/chart/composite/panel-surface.tsx +69 -8
- package/src/components/chart/composite/types.ts +6 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +1 -1
- package/src/components/command-bar/routes/root/command-items.ts +32 -6
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/data-table/view.tsx +23 -3
- package/src/components/index.ts +3 -0
- package/src/components/layout/detached-pane-shell.tsx +1 -0
- package/src/components/layout/floating-pane.tsx +1 -0
- package/src/components/layout/header.tsx +228 -55
- package/src/components/layout/pane/content.tsx +16 -14
- package/src/components/layout/pane/footer/index.tsx +22 -3
- package/src/components/layout/pane/footer/model.ts +99 -4
- package/src/components/layout/pane/header-tabs.tsx +15 -1
- package/src/components/layout/pane/header.tsx +10 -3
- package/src/components/layout/pane/index.tsx +1 -0
- package/src/components/layout/pane/pane-tabs.tsx +9 -1
- package/src/components/onboarding/onboarding-frame.tsx +9 -11
- package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
- package/src/components/onboarding/wizard-model.ts +10 -3
- package/src/components/ui/button.tsx +11 -4
- package/src/components/ui/choice-dialog.tsx +8 -5
- package/src/components/ui/display.tsx +11 -6
- package/src/components/ui/fields.tsx +100 -20
- package/src/components/ui/icon.tsx +6 -1
- package/src/components/ui/list-row-lines.tsx +87 -0
- package/src/components/ui/list-view.tsx +14 -0
- package/src/components/ui/logo-mark.tsx +64 -0
- package/src/components/ui/number-step.ts +54 -0
- package/src/components/ui/select-button.tsx +5 -4
- package/src/components/ui/select-field.tsx +3 -3
- package/src/components/ui/stat-grid.tsx +28 -3
- package/src/components/ui/status.tsx +20 -4
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- package/src/core/app-services.ts +4 -0
- package/src/core/state/app/instrument.ts +8 -3
- package/src/data/config/store/normalize.ts +4 -1
- package/src/data/plugin-cache.ts +32 -6
- package/src/docs/snapshot.json +5 -5
- package/src/i18n/es.ts +31 -3
- package/src/i18n/ja.ts +31 -3
- package/src/i18n/ko.ts +31 -3
- package/src/i18n/zh-cn.ts +32 -4
- package/src/i18n/zh-tw.ts +32 -4
- package/src/market-data/commodity-names.ts +80 -0
- package/src/market-data/coordinator/auxiliary.ts +3 -1
- package/src/market-data/coordinator/chart.ts +3 -1
- package/src/market-data/futures-board.ts +38 -1
- package/src/market-data/futures-contract.ts +91 -0
- package/src/market-data/fx-closes.ts +78 -1
- package/src/market-data/market/change-basis.ts +219 -0
- package/src/market-data/market/format.ts +28 -12
- package/src/market-data/market/freshness.ts +38 -0
- package/src/market-data/market/status.ts +55 -9
- package/src/market-data/market/year-to-date.ts +46 -0
- package/src/market-data/quotes/contributions.ts +12 -0
- package/src/market-data/quotes/delisting.ts +34 -0
- package/src/market-data/quotes/live-valuation.ts +23 -0
- package/src/market-data/quotes/resolution.ts +8 -2
- package/src/market-data/tax-equivalent.ts +10 -2
- package/src/notifications/app-notifier.ts +2 -2
- package/src/notifications/notification-log.ts +8 -6
- package/src/plugins/builtin/alerts/alert-engine.ts +3 -1
- package/src/plugins/builtin/alerts/cli.ts +26 -18
- package/src/plugins/builtin/alerts/events.ts +1 -0
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/alerts/research-rules.ts +1 -0
- package/src/plugins/builtin/analytics/risk-pane.tsx +10 -7
- package/src/plugins/builtin/attention/pane.tsx +16 -14
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/awards/pane.tsx +6 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- package/src/plugins/builtin/backtest/export.ts +1 -1
- package/src/plugins/builtin/backtest/window.ts +16 -3
- package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
- package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
- package/src/plugins/builtin/broker-manager/index.tsx +20 -9
- package/src/plugins/builtin/broker-manager/model.ts +5 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
- package/src/plugins/builtin/broker-manager/table.ts +5 -1
- package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
- package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
- package/src/plugins/builtin/broker-trading/command.ts +103 -0
- package/src/plugins/builtin/broker-trading/controller.ts +688 -0
- package/src/plugins/builtin/broker-trading/dock.tsx +184 -0
- package/src/plugins/builtin/broker-trading/headless.ts +76 -0
- package/src/plugins/builtin/broker-trading/index.tsx +77 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
- package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
- package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
- package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
- package/src/plugins/builtin/broker-trading/pane.tsx +507 -0
- package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
- package/src/plugins/builtin/broker-trading/ticket.tsx +578 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
- package/src/plugins/builtin/buildout/format.ts +1 -0
- package/src/plugins/builtin/buildout/model/index.ts +16 -7
- package/src/plugins/builtin/buildout/pane/index.tsx +11 -7
- package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
- package/src/plugins/builtin/catalysts/client.ts +2 -2
- package/src/plugins/builtin/catalysts/pane.tsx +3 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
- package/src/plugins/builtin/chart-composer/headless.ts +2 -1
- package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +32 -11
- package/src/plugins/builtin/chart-composer/presets.ts +2 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
- package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
- package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
- package/src/plugins/builtin/commodity-balances/client.ts +6 -4
- package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
- package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
- package/src/plugins/builtin/commodity-balances/model.ts +22 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +39 -9
- package/src/plugins/builtin/company-attention/app-rank.tsx +4 -5
- package/src/plugins/builtin/company-attention/pane.tsx +13 -11
- package/src/plugins/builtin/company-kpis/client.ts +3 -2
- package/src/plugins/builtin/company-kpis/headless.ts +5 -7
- package/src/plugins/builtin/company-kpis/model.ts +85 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +30 -15
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- package/src/plugins/builtin/correlation/headless.ts +2 -1
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -0
- package/src/plugins/builtin/correlation/settings.ts +1 -0
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cot/index.tsx +1 -1
- package/src/plugins/builtin/cot/model.ts +35 -12
- package/src/plugins/builtin/cot/pane.tsx +4 -3
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
- package/src/plugins/builtin/credit-documents/pane.tsx +8 -7
- package/src/plugins/builtin/crypto-board/client.ts +33 -2
- package/src/plugins/builtin/crypto-board/headless.ts +5 -2
- package/src/plugins/builtin/crypto-board/model.ts +28 -8
- package/src/plugins/builtin/debt-maturities/pane.tsx +4 -3
- package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/doe/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
- package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
- package/src/plugins/builtin/econ/calendar-model.ts +35 -0
- package/src/plugins/builtin/econ/index.tsx +31 -4
- package/src/plugins/builtin/econ/print-watch.ts +279 -0
- package/src/plugins/builtin/equity-screener/headless.ts +30 -5
- package/src/plugins/builtin/equity-screener/model.ts +68 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +129 -119
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
- package/src/plugins/builtin/exposure/client.ts +4 -1
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/exposure/evidence.ts +4 -1
- package/src/plugins/builtin/exposure/holdings.ts +3 -1
- package/src/plugins/builtin/exposure/pane.tsx +43 -21
- package/src/plugins/builtin/filing-events/index.tsx +9 -3
- package/src/plugins/builtin/futures/table.tsx +6 -3
- package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +4 -1
- package/src/plugins/builtin/gpu/changes.tsx +10 -10
- package/src/plugins/builtin/gpu/equities.tsx +4 -1
- package/src/plugins/builtin/gpu/history.tsx +4 -2
- package/src/plugins/builtin/gpu/pane.tsx +5 -3
- package/src/plugins/builtin/holders/client.ts +2 -1
- package/src/plugins/builtin/holders/headless.ts +2 -1
- package/src/plugins/builtin/holders/thirteenf-match.ts +3 -1
- package/src/plugins/builtin/insider/base-values.ts +76 -0
- package/src/plugins/builtin/insider/client.ts +3 -0
- package/src/plugins/builtin/insider/headless.ts +48 -10
- package/src/plugins/builtin/insider/index.tsx +17 -8
- package/src/plugins/builtin/insider/issuer.ts +34 -4
- package/src/plugins/builtin/insider/signals-client.ts +2 -2
- package/src/plugins/builtin/insider/table-model.ts +35 -13
- package/src/plugins/builtin/insider/table.tsx +5 -2
- package/src/plugins/builtin/iv-history/events.ts +145 -0
- package/src/plugins/builtin/iv-history/format.ts +3 -0
- package/src/plugins/builtin/iv-history/headless.ts +91 -7
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- package/src/plugins/builtin/jodi/pane.tsx +4 -4
- package/src/plugins/builtin/market-halts/model.ts +31 -6
- package/src/plugins/builtin/market-halts/pane.tsx +3 -0
- package/src/plugins/builtin/market-movers/client.ts +4 -3
- package/src/plugins/builtin/market-movers/screener.ts +4 -3
- package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/notes/cli.ts +1 -1
- package/src/plugins/builtin/notes/export.ts +17 -6
- package/src/plugins/builtin/notes/files.ts +16 -5
- package/src/plugins/builtin/options/enrichment-model.ts +10 -9
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/model-spot.ts +70 -4
- package/src/plugins/builtin/options/view.tsx +118 -35
- package/src/plugins/builtin/options-calculator/pane.tsx +7 -4
- package/src/plugins/builtin/options-positioning/headless.ts +11 -3
- package/src/plugins/builtin/options-positioning/model.ts +24 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +10 -12
- package/src/plugins/builtin/options-scenario/client.ts +19 -1
- package/src/plugins/builtin/options-scenario/headless.ts +13 -6
- package/src/plugins/builtin/options-scenario/pane.tsx +54 -11
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +5 -4
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -5
- package/src/plugins/builtin/portfolio-list/cli/render.ts +34 -10
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- package/src/plugins/builtin/power/client.ts +2 -2
- package/src/plugins/builtin/power/pane.tsx +36 -31
- package/src/plugins/builtin/primary-dealers/client.ts +137 -62
- package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
- package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
- package/src/plugins/builtin/primary-dealers/model.ts +179 -129
- package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
- package/src/plugins/builtin/research/analyst-model.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/client.ts +2 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +4 -3
- package/src/plugins/builtin/risk-factors/index.tsx +12 -3
- package/src/plugins/builtin/scanner/feed.ts +6 -1
- package/src/plugins/builtin/scanner/headless.ts +1 -1
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/client.ts +2 -1
- package/src/plugins/builtin/sectors/client.ts +4 -3
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- package/src/plugins/builtin/shared/cloud-resource.ts +15 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
- package/src/plugins/builtin/shared/free-routes.ts +24 -0
- package/src/plugins/builtin/shared/headless-options.ts +11 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +64 -0
- package/src/plugins/builtin/shared/series-list-detail.tsx +3 -1
- package/src/plugins/builtin/shared/volatility/options.ts +2 -1
- package/src/plugins/builtin/social-mentions/client.ts +2 -1
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -2
- package/src/plugins/builtin/themes/model.ts +43 -10
- package/src/plugins/builtin/themes/pane.tsx +8 -6
- package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
- package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
- package/src/plugins/builtin/time-sales/pane.tsx +2 -2
- package/src/plugins/builtin/trace-bonds/client.ts +14 -3
- package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
- package/src/plugins/builtin/vol-surface/client.ts +23 -3
- package/src/plugins/builtin/vol-surface/headless.ts +33 -14
- package/src/plugins/builtin/vol-surface/model.ts +30 -10
- package/src/plugins/builtin/vol-surface/pane.tsx +35 -15
- package/src/plugins/builtin/vol-surface/stored.ts +34 -3
- package/src/plugins/builtin/vol-surface/tables.ts +14 -0
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/compat.ts +3 -0
- package/src/plugins/external-runtime.ts +5 -0
- package/src/plugins/loader.ts +6 -0
- package/src/plugins/plugin-icon.ts +61 -0
- package/src/plugins/registry/host-actions.ts +2 -1
- package/src/plugins/registry/index.ts +9 -1
- package/src/plugins/runtime/context.tsx +5 -0
- package/src/plugins/runtime/index.tsx +3 -0
- package/src/portfolio/column-values.ts +13 -4
- package/src/portfolio/cost-fx.ts +5 -28
- package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
- package/src/public/broker.ts +8 -0
- package/src/public/market-data.ts +5 -0
- package/src/renderers/dom/desktop/control-styles.ts +7 -1
- package/src/renderers/dom/desktop/controls.tsx +40 -8
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/list-view.tsx +51 -19
- package/src/renderers/dom/desktop/menu.tsx +10 -1
- package/src/renderers/dom/desktop/query-bar.tsx +2 -0
- package/src/renderers/dom/desktop/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +22 -5
- package/src/renderers/dom/host/top-surface.ts +127 -0
- package/src/renderers/dom/styles.css +51 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +21 -1
- package/src/renderers/electrobun/bun/desktop/backend.ts +10 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +210 -30
- package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
- package/src/renderers/electrobun/view/external-plugins.ts +1 -0
- package/src/renderers/electrobun/view/news-source.ts +4 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +8 -1
- package/src/renderers/opentui/start.tsx +7 -0
- package/src/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/index.ts +57 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/provider-errors.ts +3 -1
- package/src/sources/provider-router/history.ts +49 -6
- package/src/sources/provider-router/index.ts +18 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +62 -11
- package/src/sync/local-only.ts +121 -0
- package/src/sync/react.ts +6 -0
- package/src/sync/types.ts +3 -0
- package/src/telemetry/crash-reports.ts +4 -0
- package/src/telemetry/update-failures-node.ts +59 -0
- package/src/telemetry/update-failures.ts +307 -0
- package/src/tickers/company-name-listing.ts +9 -7
- package/src/tickers/company-name.ts +43 -0
- package/src/tickers/listing-fate.ts +57 -8
- package/src/tickers/search/index.ts +24 -3
- package/src/time-series/price-comparison.ts +35 -0
- package/src/time-series/resolve.ts +52 -11
- package/src/time-series/spec.ts +1 -1
- package/src/time-series/types.ts +5 -0
- package/src/types/broker.ts +56 -1
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +11 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +19 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/issuer-listings.ts +28 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/updater/failure.ts +163 -0
- package/src/updater/index.ts +87 -23
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +28 -1
- package/src/utils/currency-units.ts +32 -1
- package/src/utils/dom-screenshot.ts +22 -9
- package/src/utils/exchange-notation.generated.ts +360 -0
- package/src/utils/exchanges.ts +15 -322
- package/src/utils/format.ts +31 -1
- package/src/utils/instrument-identity.ts +38 -1
- package/src/utils/options.ts +46 -1
- package/src/utils/price-history.ts +4 -0
- package/src/utils/reported-money.ts +8 -4
- package/src/utils/sec.ts +68 -0
|
@@ -224,6 +224,15 @@ export function resolvePortfolioAccountState(
|
|
|
224
224
|
};
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
/**
|
|
228
|
+
* What the day's move is measured from, after it, as every change line says
|
|
229
|
+
* it ("vs Fri 16 Oct close", "vs mixed closes"), so a narrow row never shows
|
|
230
|
+
* the move without it.
|
|
231
|
+
*/
|
|
232
|
+
function dayBasisPart(totals: Pick<PortfolioSummaryTotals, "dayChangeBasis">): PortfolioSummarySegment["parts"] {
|
|
233
|
+
return totals.dayChangeBasis ? [{ text: totals.dayChangeBasis, tone: "muted" }] : [];
|
|
234
|
+
}
|
|
235
|
+
|
|
227
236
|
/** Headline numbers in priority order, so the narrowest header row keeps the most important ones. */
|
|
228
237
|
export function buildPortfolioSummarySegments({
|
|
229
238
|
totals,
|
|
@@ -258,6 +267,7 @@ export function buildPortfolioSummarySegments({
|
|
|
258
267
|
? [createSummarySegment("avg-day", [
|
|
259
268
|
{ text: "Avg Day", tone: "label" },
|
|
260
269
|
{ text: formatPercentRaw(totals.avgWatchlistChange), tone: "value", color: priceColor(totals.avgWatchlistChange), bold: true },
|
|
270
|
+
...dayBasisPart(totals),
|
|
261
271
|
])]
|
|
262
272
|
: [];
|
|
263
273
|
}
|
|
@@ -332,6 +342,8 @@ export function buildPortfolioSummarySegments({
|
|
|
332
342
|
{ text: "Day", tone: "label" },
|
|
333
343
|
{ text: money(accountMetrics.dailyPnl, true), tone: "value", color: priceColor(accountMetrics.dailyPnl), bold: true },
|
|
334
344
|
{ text: `(${formatPercentRaw(accountMetrics.dailyPnlPct)})`, tone: "muted", color: priceColor(accountMetrics.dailyPnlPct) },
|
|
345
|
+
// The broker's own day is from its account's close, not the quotes': it names none.
|
|
346
|
+
...(accountMetrics.brokerDay || !Number.isFinite(accountMetrics.dailyPnl) ? [] : dayBasisPart(totals)),
|
|
335
347
|
]));
|
|
336
348
|
candidates.push(createSummarySegment("pnl", [
|
|
337
349
|
{ text: !Number.isFinite(account?.unrealizedPnl)
|
|
@@ -3,6 +3,7 @@ import type { Portfolio, TickerRecord } from "../../../../types/ticker";
|
|
|
3
3
|
import { convertCurrency, formatCompactAmount } from "../../../../utils/format";
|
|
4
4
|
import { getCurrencySymbol } from "../../../../market-data/market/format";
|
|
5
5
|
import { getActiveQuoteDisplay } from "../../../../market-data/market/status";
|
|
6
|
+
import { combinedChangeBasisLabel, quoteChangeBasis, type QuoteChangeBasis } from "../../../../market-data/market/change-basis";
|
|
6
7
|
import { isQuoteStaleForCurrentSession } from "../../../../market-data/quotes/freshness";
|
|
7
8
|
import { headlineQuoteDisplay } from "../../../../portfolio/column-values";
|
|
8
9
|
import { isManualPortfolio } from "../../../../portfolio/mutations";
|
|
@@ -45,6 +46,13 @@ export interface PortfolioSummaryTotals {
|
|
|
45
46
|
unrealizedPnlBasis?: PortfolioPositionPnl["basis"];
|
|
46
47
|
dailyPnl: number;
|
|
47
48
|
dailyPnlPct: number;
|
|
49
|
+
/**
|
|
50
|
+
* What Day (or a watchlist's Avg Day) is measured from, as each holding's
|
|
51
|
+
* change line says it: "vs Fri 16 Oct close" when they agree, "vs mixed
|
|
52
|
+
* closes" when they do not (an HKEX listing beside a US one). Absent
|
|
53
|
+
* without a move.
|
|
54
|
+
*/
|
|
55
|
+
dayChangeBasis?: string;
|
|
48
56
|
totalCostBasis: number;
|
|
49
57
|
hasPositions: boolean;
|
|
50
58
|
unrealizedPnl: number;
|
|
@@ -131,6 +139,7 @@ export function calculatePortfolioSummaryTotals(
|
|
|
131
139
|
let hasPositions = false;
|
|
132
140
|
let watchlistChangeSum = 0;
|
|
133
141
|
let watchlistCount = 0;
|
|
142
|
+
const dayBases: Array<QuoteChangeBasis | null> = [];
|
|
134
143
|
const unavailableConversions = new Set<string>();
|
|
135
144
|
let livePriced = true;
|
|
136
145
|
const pricedLots: PricedPortfolioLot[] = [];
|
|
@@ -150,11 +159,12 @@ export function calculatePortfolioSummaryTotals(
|
|
|
150
159
|
const quoteCurrency = quote?.currency || ticker.metadata.currency || "USD";
|
|
151
160
|
|
|
152
161
|
if (!isPortfolio) {
|
|
153
|
-
// The CHG% column's value, so Avg Day is the mean of that column.
|
|
154
|
-
const changePercent = headlineQuoteDisplay(activeQuote, quote)?.changePercent;
|
|
162
|
+
// The CHG% column's value, so Avg Day is the mean of that column; a listing that no longer trades has none.
|
|
163
|
+
const changePercent = quote?.delisting ? undefined : headlineQuoteDisplay(activeQuote, quote)?.changePercent;
|
|
155
164
|
if (changePercent != null) {
|
|
156
165
|
watchlistChangeSum += changePercent;
|
|
157
166
|
watchlistCount++;
|
|
167
|
+
dayBases.push(quoteChangeBasis(quote));
|
|
158
168
|
}
|
|
159
169
|
continue;
|
|
160
170
|
}
|
|
@@ -220,9 +230,18 @@ export function calculatePortfolioSummaryTotals(
|
|
|
220
230
|
totalMktValue += marketValue.gross;
|
|
221
231
|
netMktValue += marketValue.net;
|
|
222
232
|
if (activeQuote && Number.isFinite(grossPriceUnits)) {
|
|
223
|
-
|
|
233
|
+
// A listing that no longer trades does not move: its last session's change is not today's.
|
|
234
|
+
const previousClose = quote?.delisting ? activeQuote.price
|
|
235
|
+
: activeQuote.change != null ? activeQuote.price - activeQuote.change : Number.NaN;
|
|
224
236
|
totalPrevValue += toBaseQuote(grossPriceUnits * previousClose);
|
|
225
237
|
signedDailyPnl += toBaseQuote(totalPriceUnits * (activeQuote.price - previousClose));
|
|
238
|
+
// The move valued: the latest print's from the daily reference, the regular close's from the session before it.
|
|
239
|
+
if (!quote?.delisting) {
|
|
240
|
+
dayBases.push(quoteChangeBasis(quote, {
|
|
241
|
+
session: valuationBasis === "regular-close" ? "regular" : "active",
|
|
242
|
+
terms: futuresTerms?.get(ticker.metadata.ticker),
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
226
245
|
} else {
|
|
227
246
|
// A broker mark is not evidence of an unchanged day.
|
|
228
247
|
signedDailyPnl = Number.NaN;
|
|
@@ -242,6 +261,7 @@ export function calculatePortfolioSummaryTotals(
|
|
|
242
261
|
const unrealizedPnl = signedUnrealizedPnl;
|
|
243
262
|
const unrealizedPnlPct = portfolioPnlPercent(unrealizedPnl, totalCostBasis) ?? Number.NaN;
|
|
244
263
|
const avgWatchlistChange = watchlistCount > 0 ? watchlistChangeSum / watchlistCount : 0;
|
|
264
|
+
const dayChangeBasis = combinedChangeBasisLabel(dayBases, now);
|
|
245
265
|
|
|
246
266
|
return {
|
|
247
267
|
totalMktValue,
|
|
@@ -258,6 +278,7 @@ export function calculatePortfolioSummaryTotals(
|
|
|
258
278
|
: pnlBases.size > 1 ? "mixed" : [...pnlBases][0],
|
|
259
279
|
dailyPnl,
|
|
260
280
|
dailyPnlPct,
|
|
281
|
+
...(dayChangeBasis ? { dayChangeBasis } : {}),
|
|
261
282
|
totalCostBasis,
|
|
262
283
|
hasPositions,
|
|
263
284
|
unrealizedPnl,
|
|
@@ -106,11 +106,11 @@ export async function fetchPowerTickerScopes(symbol: string, tab: PowerTab, cont
|
|
|
106
106
|
}
|
|
107
107
|
const key = (query: unknown, scope: string) => `${scope}:${JSON.stringify(query)}`;
|
|
108
108
|
export const loadPowerBoard = (filter: PowerFilter, scope: string, force = false, signal?: AbortSignal) =>
|
|
109
|
-
loadCloudResource(powerBoardCache, key(filter, scope), () => fetchPowerBoard(filter, apiClient, signal), { force, validate: validatePowerBoard });
|
|
109
|
+
loadCloudResource(powerBoardCache, key(filter, scope), () => fetchPowerBoard(filter, apiClient, signal), { force, validate: validatePowerBoard, signal });
|
|
110
110
|
export const loadPowerHistory = (filter: PowerFilter, scope: string, force = false) =>
|
|
111
111
|
loadCloudResource(powerHistoryCache, key(filter, scope), () => fetchPowerHistory(filter), { force, validate: validatePowerHistory });
|
|
112
112
|
export const loadPowerDetail = (id: string, scope: string, force = false, offset = 0, signal?: AbortSignal) => loadCloudResource(powerDetailCache, key({ id, offset }, scope),
|
|
113
113
|
async () => { const value = validateDetail(await apiClient.getCloudPowerProject(id, { offset, limit: 100 }, signal));
|
|
114
114
|
if (value.hasMore && value.nextOffset! <= offset) throw new Error("Project revision pagination did not advance.");
|
|
115
115
|
if (value.project && value.project.id !== id || value.revisions.some((r) => r.id !== id)) throw new Error("Project evidence belongs to a different project.");
|
|
116
|
-
return value; }, { force, validate: validateDetail });
|
|
116
|
+
return value; }, { force, validate: validateDetail, signal });
|
|
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
|
2
2
|
import type { PowerBoard, PowerFilter, PowerHistory } from "../../../api-client/power";
|
|
3
3
|
import { isAccessDenied } from "../../../api-client/errors";
|
|
4
4
|
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
5
|
-
import { Button, ChartTableHeader, DataTableStackView, EmptyState,
|
|
5
|
+
import { Button, ChartTableHeader, DataTableStackView, EmptyState, QueryBar, useChartTableSelection, usePagedRows, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, useQueryBarSearch, useTableLoadMore, type DataTableCell, type PaneHint, type QueryBarFilter } from "../../../components";
|
|
6
6
|
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
7
7
|
import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginAppActions, usePluginPaneState } from "../../../public/react";
|
|
8
8
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
@@ -97,6 +97,10 @@ function PowerView({ width, height, focused, scope, needsVerification }: PanePro
|
|
|
97
97
|
}, [filter, scope, projectTab, snapshot]);
|
|
98
98
|
const pages = usePagedRows(loader, { getId: (row) => row.id });
|
|
99
99
|
const board = isAccessDenied(pages.error) || isAccessDenied(pages.moreError) ? undefined : pages.pages[0]?.payload;
|
|
100
|
+
// A tab or filter change starts its list over; the filter menus keep the last board's choices until the new one answers.
|
|
101
|
+
const lastFilters = useRef(board?.filters);
|
|
102
|
+
if (board) lastFilters.current = board.filters;
|
|
103
|
+
const filterChoices = board?.filters ?? lastFilters.current;
|
|
100
104
|
const historyLoader = useCallback(async ({ offset, force }: { offset: number; force: boolean }) => {
|
|
101
105
|
const result = snapshot?.history ? { payload: snapshot.history, stale: false, refreshError: null }
|
|
102
106
|
: await loadPowerHistory({ ...filter, offset, limit: 500 }, scope, force);
|
|
@@ -157,7 +161,8 @@ function PowerView({ width, height, focused, scope, needsVerification }: PanePro
|
|
|
157
161
|
const notices = [pages.moreError?.message, historyPages.moreError?.message, ...pages.pages.map((p) => p.refreshError), historyData?.refreshError,
|
|
158
162
|
board && tab !== "capacity" ? powerCoverageNote(board.coverage) : null].filter((v): v is string => !!v);
|
|
159
163
|
usePaneNoticeFooter({ registrationId: "power:notices", focused, notices: [...new Set(notices)], enabled: !openRow });
|
|
160
|
-
const
|
|
164
|
+
const signInWall = !board && isCloudSessionRequired(error);
|
|
165
|
+
const { strip, rows: tabRows } = usePaneTabs(!signInWall ? { tabs: [...POWER_TABS], activeValue: tab, onSelect: setTab, focused, dense: true } : null);
|
|
161
166
|
const baseColumns = tab === "capacity" && context === "generation" ? GENERATION_COLUMNS : tab === "capacity" && context === "utility" ? UTILITY_COLUMNS : projectTab ? PROJECT_COLUMNS : tab === "history" ? HISTORY_COLUMNS : tab === "outcomes" ? RATE_COLUMNS : tab === "utilities" ? EXPOSURE_COLUMNS : COVERAGE_COLUMNS;
|
|
162
167
|
const records = useMemo(() => rows.filter(isRecord), [rows]);
|
|
163
168
|
const columns = useMemo(() => withoutQuietColumns(baseColumns, records, (r, id) => r.cells[id] ?? "", QUIET_COLUMNS), [baseColumns, records]);
|
|
@@ -167,10 +172,10 @@ function PowerView({ width, height, focused, scope, needsVerification }: PanePro
|
|
|
167
172
|
const filters: QueryBarFilter[] = [
|
|
168
173
|
...(historical && ["queue", "history", "outcomes"].includes(tab) ? [{ id: "source", label: "History", value: sourceId, defaultValue: "", options: [{ value: "", label: "All benchmarks" }, { value: "us-lbnl-annual", label: "Annual benchmark" }, { value: "us-lbnl", label: "Project benchmark" }], onChange: setSourceId }] : []),
|
|
169
174
|
...(["loads", "utilities"].includes(tab) ? [{ id: "loadClass", label: "Loads", value: loadClass, defaultValue: "", options: [{ value: "", label: "All loads" }, { value: "datacenter", label: "Datacenters" }], onChange: setLoadClass }] : []),
|
|
170
|
-
{ id: "country", label: "Country", value: country, defaultValue: "", options: choices(
|
|
171
|
-
{ id: "region", label: "Region", value: region, defaultValue: "", options: choices(
|
|
172
|
-
...(!["loads", "utilities", "coverage"].includes(tab) ? [{ id: "fuel", label: "Fuel", value: fuel, defaultValue: "", options: choices(
|
|
173
|
-
...(tab !== "coverage" ? [{ id: "status", label: "Status", value: status, defaultValue: "", options: choices(
|
|
175
|
+
{ id: "country", label: "Country", value: country, defaultValue: "", options: choices(filterChoices?.countries), onChange: setCountry },
|
|
176
|
+
{ id: "region", label: "Region", value: region, defaultValue: "", options: choices(filterChoices?.regions), onChange: setRegion },
|
|
177
|
+
...(!["loads", "utilities", "coverage"].includes(tab) ? [{ id: "fuel", label: "Fuel", value: fuel, defaultValue: "", options: choices(filterChoices?.fuels), onChange: setFuel }] : []),
|
|
178
|
+
...(tab !== "coverage" ? [{ id: "status", label: "Status", value: status, defaultValue: "", options: choices(filterChoices?.statuses), onChange: setStatus }] : []),
|
|
174
179
|
...(["queue", "history", "outcomes"].includes(tab) ? [{ id: "historical", label: "Benchmark", kind: "toggle" as const, value: historical, defaultValue: false, onChange: setHistorical }] : []),
|
|
175
180
|
...(tab === "capacity" ? [{ id: "context", label: "Context", value: context, options: [{ value: "capacity", label: "Capacity" }, { value: "generation", label: "Generation" }, { value: "utility", label: "Utilities" }], onChange: setContext }] : []),
|
|
176
181
|
];
|
|
@@ -206,31 +211,31 @@ function PowerView({ width, height, focused, scope, needsVerification }: PanePro
|
|
|
206
211
|
color: column.id === "name" || column.id === "utility" || column.id === "capacityMw" || column.id === "requestedMw" ? colors.textBright
|
|
207
212
|
: ["location", "asOf", "observedAt", "scope", "proposedDate", "region", "country", "kind", "role", "period"].includes(column.id) ? colors.textDim : colors.text };
|
|
208
213
|
};
|
|
209
|
-
if (
|
|
214
|
+
if (signInWall) return <SignInWall placement="power-signin" action="view power and interconnection data" needsVerification={needsVerification} />;
|
|
215
|
+
// The strip and the query bar stay through every load: a tab or filter change waits in the table's body.
|
|
210
216
|
return <Box width={width} height={height} flexDirection="column">{strip}
|
|
211
|
-
<
|
|
212
|
-
{board ?
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
</PaneStatusBody>
|
|
217
|
+
<DataTableStackView<PowerRow> columns={columns} items={board ? items : []} getItemKey={rowId} focused={focused && !searchFocus.active}
|
|
218
|
+
status={board ? undefined : { loading: pages.loading, error, subject: "power and interconnection data" }}
|
|
219
|
+
rootWidth={width} rootHeight={bodyHeight} scrollRef={scrollRef} onBodyScrollActivity={loadMore} resetScrollKey={`${tab}:${queryKey}`}
|
|
220
|
+
selection={{ kind: "id", selectedId: selected?.id ?? null, getId: rowId, onChange: setSelectedId }}
|
|
221
|
+
onActivate={(row) => row.locked ? upgrade() : setOpenId(row.id)} detailOpen={!!openRow} onBack={() => setOpenId(null)} detailTitle={openRow?.label}
|
|
222
|
+
detailScrollRef={detailScrollRef} detailContent={openRow ? <PowerDetailView row={openRow} scope={scope} scrollRef={detailScrollRef} snapshot={!!snapshot} focused={focused} width={width} height={bodyHeight - 1} /> : null}
|
|
223
|
+
renderCell={cell} freezeFirstColumn showHorizontalScrollbar selectedTextOverridesCellColor
|
|
224
|
+
isNavigable={isRecord} renderSectionHeader={(row) => row.section ? { text: `${row.label} (${row.section.count})` } : null}
|
|
225
|
+
sortColumnId={projectTab ? sort.column ?? null : null} sortDirection={sort.direction}
|
|
226
|
+
isColumnSortable={(column) => projectTab && POWER_SORT_COLUMNS.includes(column.id)}
|
|
227
|
+
onHeaderClick={projectTab ? (column) => { if (POWER_SORT_COLUMNS.includes(column)) setSort((old) => ({ column: column as PowerFilter["sort"], direction: old.column === column && old.direction === "desc" ? "asc" : "desc" })); } : undefined}
|
|
228
|
+
rootBefore={<ChartTableHeader width={width} height={bodyHeight} tableRows={items.length} tableColumns={columns} query={query}
|
|
229
|
+
figures={projectTab && tab === "queue" && board?.access === "full" ? queueFigures : undefined}
|
|
230
|
+
chart={board && tab === "history" ? { series, ...chartLink, formatValue: (n) => `${powerNumber(n)} MW`, loading: historyPages.loading || historyPages.hasMore && !historyPages.moreError,
|
|
231
|
+
empty: series.length ? undefined : historyPages.moreError ? "Full history could not load. Refresh to retry." : historyData?.payload.access === "preview" ? "Full history is available with Pro." : "History is accumulating from recorded snapshots.", remoteKind: "power-history" } : null} />}
|
|
232
|
+
emptyStateTitle={tab === "history" ? historyPages.error?.message ?? "No recorded history for these filters." : "No reported records match these filters."}
|
|
233
|
+
emptyContent={tickerMessage ? <Box paddingX={1} paddingY={1}>
|
|
234
|
+
<EmptyState title={tickerMessage.title} hint={tickerMessage.hint || undefined}
|
|
235
|
+
actions={tickerMessage.found.length ? tickerMessage.found.map((scope) => <Button key={`${scope.tab}:${scope.context}`} label={`Open ${scope.view}`}
|
|
236
|
+
onPress={() => { if (scope.tab === "capacity") setContext(scope.context); setTab(scope.tab); }} />) : undefined} />
|
|
237
|
+
</Box> : undefined}
|
|
238
|
+
bodyAfter={locked && desktop ? <LockedOverlay rows={Math.min(3, locked)} text="Unlock full power data with Pro" onPress={upgrade} role="power-upgrade" /> : undefined}
|
|
239
|
+
/>
|
|
235
240
|
</Box>;
|
|
236
241
|
}
|
|
@@ -2,26 +2,17 @@ import { httpFetch } from "../../../utils/http-transport";
|
|
|
2
2
|
import { isoDateOrNull, numericOrNull, recordOrNull, textOrNull } from "../../../utils/guards";
|
|
3
3
|
import {
|
|
4
4
|
boardAsOf,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
buildDealerRows,
|
|
6
|
+
newestSeriesBreak,
|
|
7
|
+
sumSeries,
|
|
8
8
|
type DealerRow,
|
|
9
|
-
type
|
|
9
|
+
type SeriesPoint,
|
|
10
10
|
} from "./model";
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const API = "https://markets.newyorkfed.org/api/pd";
|
|
13
13
|
const FETCH_TIMEOUT_MS = 15_000;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
asOf: string;
|
|
17
|
-
value: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface ParsedSeries {
|
|
21
|
-
keyid: string;
|
|
22
|
-
latest: SeriesPoint | null;
|
|
23
|
-
previous: SeriesPoint | null;
|
|
24
|
-
}
|
|
14
|
+
/** The release is weekly; a series read is kept this long so moving through the board never asks twice. */
|
|
15
|
+
const HISTORY_TTL_MS = 30 * 60_000;
|
|
25
16
|
|
|
26
17
|
export interface DealerBoard {
|
|
27
18
|
seriesBreak: string;
|
|
@@ -30,52 +21,57 @@ export interface DealerBoard {
|
|
|
30
21
|
asOf: string | null;
|
|
31
22
|
}
|
|
32
23
|
|
|
33
|
-
|
|
24
|
+
/** A row's weekly history: the sum of the series behind it, oldest first. */
|
|
25
|
+
export interface DealerHistory {
|
|
26
|
+
rowId: string;
|
|
27
|
+
points: SeriesPoint[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function timeseriesRows(payload: unknown): unknown[] {
|
|
34
31
|
const root = recordOrNull(payload);
|
|
35
32
|
const pd = recordOrNull(root?.pd);
|
|
36
|
-
|
|
37
|
-
const entries: CatalogEntry[] = [];
|
|
38
|
-
for (const raw of rows) {
|
|
39
|
-
const row = recordOrNull(raw);
|
|
40
|
-
if (!row) continue;
|
|
41
|
-
const seriesBreak = textOrNull(row.seriesbreak) ?? textOrNull(row.seriesBreak);
|
|
42
|
-
const keyid = textOrNull(row.keyid) ?? textOrNull(row.keyId);
|
|
43
|
-
if (!seriesBreak || !keyid) continue;
|
|
44
|
-
entries.push({ seriesBreak, keyid, description: textOrNull(row.description) ?? "" });
|
|
45
|
-
}
|
|
46
|
-
return entries;
|
|
33
|
+
return Array.isArray(pd?.timeseries) ? pd.timeseries : Array.isArray(root?.timeseries) ? root.timeseries : [];
|
|
47
34
|
}
|
|
48
35
|
|
|
49
|
-
|
|
50
|
-
function parseSeriesObservations(payload: unknown, fallbackKeyid = ""): ParsedSeries {
|
|
36
|
+
function parseSeriesBreaks(payload: unknown): Array<{ seriesBreak: string; startDate: string }> {
|
|
51
37
|
const root = recordOrNull(payload);
|
|
52
38
|
const pd = recordOrNull(root?.pd);
|
|
53
|
-
const rows = Array.isArray(pd?.
|
|
54
|
-
const
|
|
39
|
+
const rows = Array.isArray(pd?.seriesbreaks) ? pd.seriesbreaks : [];
|
|
40
|
+
const breaks: Array<{ seriesBreak: string; startDate: string }> = [];
|
|
55
41
|
for (const raw of rows) {
|
|
56
42
|
const row = recordOrNull(raw);
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
if (!asOf || value == null) continue;
|
|
61
|
-
points.push({
|
|
62
|
-
asOf,
|
|
63
|
-
value,
|
|
64
|
-
keyid: textOrNull(row.keyid) ?? fallbackKeyid,
|
|
65
|
-
});
|
|
43
|
+
const seriesBreak = textOrNull(row?.seriesbreak) ?? textOrNull(row?.seriesBreak);
|
|
44
|
+
const startDate = isoDateOrNull(row?.startdate) ?? isoDateOrNull(row?.startDate);
|
|
45
|
+
if (seriesBreak && startDate) breaks.push({ seriesBreak, startDate });
|
|
66
46
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
47
|
+
return breaks;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The newest observation of every series that has a figure (the release writes `*` for a suppressed one). */
|
|
51
|
+
function parseLatest(payload: unknown): Map<string, SeriesPoint> {
|
|
52
|
+
const latest = new Map<string, SeriesPoint>();
|
|
53
|
+
for (const raw of timeseriesRows(payload)) {
|
|
54
|
+
const row = recordOrNull(raw);
|
|
55
|
+
const keyid = textOrNull(row?.keyid) ?? textOrNull(row?.keyId);
|
|
56
|
+
const asOf = isoDateOrNull(row?.asofdate) ?? isoDateOrNull(row?.asOfDate);
|
|
57
|
+
const value = numericOrNull(row?.value);
|
|
58
|
+
if (!keyid || !asOf || value == null) continue;
|
|
59
|
+
const known = latest.get(keyid);
|
|
60
|
+
if (!known || asOf > known.asOf) latest.set(keyid, { asOf, value });
|
|
61
|
+
}
|
|
62
|
+
return latest;
|
|
75
63
|
}
|
|
76
64
|
|
|
77
|
-
|
|
78
|
-
|
|
65
|
+
/** Every observation of one series, oldest first. */
|
|
66
|
+
function parseSeriesPoints(payload: unknown): SeriesPoint[] {
|
|
67
|
+
const points: SeriesPoint[] = [];
|
|
68
|
+
for (const raw of timeseriesRows(payload)) {
|
|
69
|
+
const row = recordOrNull(raw);
|
|
70
|
+
const asOf = isoDateOrNull(row?.asofdate) ?? isoDateOrNull(row?.asOfDate);
|
|
71
|
+
const value = numericOrNull(row?.value);
|
|
72
|
+
if (asOf && value != null) points.push({ asOf, value });
|
|
73
|
+
}
|
|
74
|
+
return points.sort((a, b) => (a.asOf < b.asOf ? -1 : a.asOf > b.asOf ? 1 : 0));
|
|
79
75
|
}
|
|
80
76
|
|
|
81
77
|
async function readJson(url: string): Promise<unknown> {
|
|
@@ -87,22 +83,101 @@ async function readJson(url: string): Promise<unknown> {
|
|
|
87
83
|
return response.json() as Promise<unknown>;
|
|
88
84
|
}
|
|
89
85
|
|
|
86
|
+
/** The series break changes about every two years; one read serves the session. */
|
|
87
|
+
let seriesBreakRead: Promise<string> | null = null;
|
|
88
|
+
|
|
89
|
+
function currentSeriesBreak(force: boolean): Promise<string> {
|
|
90
|
+
if (!seriesBreakRead || force) {
|
|
91
|
+
const read = readJson(`${API}/list/seriesbreaks.json`).then((payload) => {
|
|
92
|
+
const seriesBreak = newestSeriesBreak(parseSeriesBreaks(payload));
|
|
93
|
+
if (!seriesBreak) throw new Error("No series break was listed.");
|
|
94
|
+
return seriesBreak;
|
|
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;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The board in two reads: the break the release is in now, and the latest
|
|
105
|
+
* figure of every series in it together with each one's change from the week
|
|
106
|
+
* before. History is not read here; see `loadDealerHistory`.
|
|
107
|
+
*/
|
|
90
108
|
export async function fetchPrimaryDealers(): Promise<DealerBoard> {
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
...selected.positions.map((entry) => ({ entry, tab: "positions" as const })),
|
|
94
|
-
...selected.fails.map((entry) => ({ entry, tab: "fails" as const })),
|
|
95
|
-
];
|
|
96
|
-
if (jobs.length === 0) throw new Error("No position series were listed.");
|
|
97
|
-
const rows = (await Promise.all(jobs.map(async ({ entry, tab }) => {
|
|
98
|
-
const parsed = parseSeriesObservations(await readJson(seriesUrl(entry.seriesBreak, entry.keyid)), entry.keyid);
|
|
99
|
-
return toDealerRow(entry, parsed, tab);
|
|
100
|
-
}))).filter((row) => row.latest != null);
|
|
109
|
+
const seriesBreak = await currentSeriesBreak(false);
|
|
110
|
+
const rows = buildDealerRows(parseLatest(await readJson(`${API}/latest/${encodeURIComponent(seriesBreak)}.json`)));
|
|
101
111
|
if (rows.length === 0) throw new Error("No positions were recognized.");
|
|
102
112
|
return {
|
|
103
|
-
seriesBreak
|
|
113
|
+
seriesBreak,
|
|
104
114
|
positions: rows.filter((row) => row.tab === "positions"),
|
|
105
115
|
fails: rows.filter((row) => row.tab === "fails"),
|
|
106
116
|
asOf: boardAsOf(rows),
|
|
107
117
|
};
|
|
108
118
|
}
|
|
119
|
+
|
|
120
|
+
interface SeriesRead {
|
|
121
|
+
at: number;
|
|
122
|
+
read: Promise<SeriesPoint[]>;
|
|
123
|
+
points: SeriesPoint[] | null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const seriesReads = new Map<string, SeriesRead>();
|
|
127
|
+
|
|
128
|
+
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
|
+
const url = `${API}/get/${encodeURIComponent(seriesBreak)}/timeseries/${encodeURIComponent(key)}.json`;
|
|
133
|
+
const entry: SeriesRead = {
|
|
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;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** A row's history, one read per series behind it, each kept for the session. */
|
|
148
|
+
export async function loadDealerHistory(
|
|
149
|
+
seriesBreak: string,
|
|
150
|
+
row: Pick<DealerRow, "id" | "keys">,
|
|
151
|
+
force = false,
|
|
152
|
+
): Promise<DealerHistory> {
|
|
153
|
+
const series = await Promise.all(row.keys.map((key) => seriesRead(seriesBreak, key, force)));
|
|
154
|
+
return { rowId: row.id, points: sumSeries(series) };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** The points of every series behind the row when they are all in hand and fresh. */
|
|
158
|
+
function heldSeries(seriesBreak: string, row: Pick<DealerRow, "keys">): SeriesPoint[][] | null {
|
|
159
|
+
const series: SeriesPoint[][] = [];
|
|
160
|
+
for (const key of row.keys) {
|
|
161
|
+
const known = seriesReads.get(`${seriesBreak}:${key}`);
|
|
162
|
+
if (!known?.points || Date.now() - known.at >= HISTORY_TTL_MS) return null;
|
|
163
|
+
series.push(known.points);
|
|
164
|
+
}
|
|
165
|
+
return series;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Whether the row's history is in hand, so a chart can follow the selection at once. */
|
|
169
|
+
export function hasDealerHistory(seriesBreak: string, row: Pick<DealerRow, "keys">): boolean {
|
|
170
|
+
return heldSeries(seriesBreak, row) != null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** The history when every series behind the row is already in hand, so a chart can draw before any read. */
|
|
174
|
+
export function cachedDealerHistory(seriesBreak: string, row: Pick<DealerRow, "id" | "keys">): DealerHistory | null {
|
|
175
|
+
const series = heldSeries(seriesBreak, row);
|
|
176
|
+
return series ? { rowId: row.id, points: sumSeries(series) } : null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Forgets what was read, for a test or a refresh that must see the release again. */
|
|
180
|
+
export function resetPrimaryDealersCache(): void {
|
|
181
|
+
seriesBreakRead = null;
|
|
182
|
+
seriesReads.clear();
|
|
183
|
+
}
|
|
@@ -5,19 +5,26 @@ import { formatChange, formatLevel, type DealerRow } from "./model";
|
|
|
5
5
|
const level = (value: unknown) => typeof value === "number" ? formatLevel(value) : "—";
|
|
6
6
|
const change = (value: unknown) => typeof value === "number" ? formatChange(value) : "—";
|
|
7
7
|
const text = (value: unknown) => typeof value === "string" && value ? value : "—";
|
|
8
|
+
const group = (value: unknown) => typeof value === "string" ? value : "";
|
|
8
9
|
|
|
9
10
|
const COLUMNS: HeadlessPaneColumn[] = [
|
|
10
11
|
{ key: "position", header: "Position" },
|
|
12
|
+
{ key: "group", header: "Group", format: group },
|
|
11
13
|
{ key: "asOf", header: "As of", format: text },
|
|
12
14
|
{ key: "latest", header: "Latest $mn", align: "right", format: level },
|
|
13
15
|
{ key: "previous", header: "Previous $mn", align: "right", format: level },
|
|
14
16
|
{ key: "change", header: "Change $mn", align: "right", format: change },
|
|
15
17
|
];
|
|
16
18
|
|
|
19
|
+
/** The report's table sets a breakdown in under its total, as the pane does; the export names the total in `group` instead. */
|
|
20
|
+
const TEXT_COLUMNS: HeadlessPaneColumn[] = COLUMNS.filter((column) => column.key !== "group");
|
|
21
|
+
|
|
17
22
|
function reportRow(row: DealerRow) {
|
|
18
23
|
return {
|
|
19
|
-
id: row.
|
|
24
|
+
id: row.id,
|
|
20
25
|
position: row.label,
|
|
26
|
+
group: row.parent,
|
|
27
|
+
series: row.keys.join("+"),
|
|
21
28
|
asOf: row.asOf,
|
|
22
29
|
latest: row.latest,
|
|
23
30
|
previous: row.previous,
|
|
@@ -25,6 +32,24 @@ function reportRow(row: DealerRow) {
|
|
|
25
32
|
};
|
|
26
33
|
}
|
|
27
34
|
|
|
35
|
+
function textRow(row: DealerRow) {
|
|
36
|
+
return { ...reportRow(row), position: `${" ".repeat(row.depth)}${row.label}` };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function section(title: string, rows: readonly DealerRow[]) {
|
|
40
|
+
return {
|
|
41
|
+
title,
|
|
42
|
+
columns: COLUMNS,
|
|
43
|
+
rows: rows.map(reportRow),
|
|
44
|
+
textColumns: TEXT_COLUMNS,
|
|
45
|
+
textRows: rows.map(textRow),
|
|
46
|
+
unit: "USD millions",
|
|
47
|
+
rowSymbolKey: "series",
|
|
48
|
+
rowAsOfKey: "asOf",
|
|
49
|
+
interval: "weekly",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
28
53
|
export const primaryDealersHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
29
54
|
shape: "bundle",
|
|
30
55
|
argument: { kind: "none" },
|
|
@@ -35,8 +60,8 @@ export const primaryDealersHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
35
60
|
aliases: ["PDLR"],
|
|
36
61
|
dataRequirements: ["Weekly primary dealer positions and settlement fails"],
|
|
37
62
|
limitations: [
|
|
38
|
-
"
|
|
39
|
-
"Latest observation and the
|
|
63
|
+
"Net positions in Treasuries (bills, coupons by tenor, TIPS, FRN), agencies, MBS, corporates, munis and ABS; settlement fails in Treasuries, TIPS and MBS",
|
|
64
|
+
"Latest observation and the change from the week before; the weekly history is in the pane",
|
|
40
65
|
],
|
|
41
66
|
},
|
|
42
67
|
async load() {
|
|
@@ -44,8 +69,8 @@ export const primaryDealersHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
44
69
|
return {
|
|
45
70
|
complete: board.positions.length > 0,
|
|
46
71
|
sections: [
|
|
47
|
-
|
|
48
|
-
|
|
72
|
+
section("Positions", board.positions),
|
|
73
|
+
section("Fails", board.fails),
|
|
49
74
|
],
|
|
50
75
|
metadata: { asOf: board.asOf, seriesBreak: board.seriesBreak },
|
|
51
76
|
};
|
|
@@ -8,8 +8,9 @@ export const primaryDealersModule: PluginModule = {
|
|
|
8
8
|
id: "primary-dealers-pane",
|
|
9
9
|
paneId: PRIMARY_DEALERS_PANE_ID,
|
|
10
10
|
label: "Primary Dealer Positions",
|
|
11
|
-
description: "Weekly net positions
|
|
12
|
-
|
|
11
|
+
description: "Weekly net positions with history, by sector and Treasury tenor, and settlement fails.",
|
|
12
|
+
// No "muni": a muni search lands on DVD's muni funds alone (known-gaps.test.ts), not on a table of two functions.
|
|
13
|
+
keywords: ["primary", "dealer", "dealers", "positions", "fails", "treasury", "bills", "coupons", "tips", "frn", "mbs", "pdlr"],
|
|
13
14
|
shortcut: { prefix: "PDLR" },
|
|
14
15
|
headless: primaryDealersHeadless,
|
|
15
16
|
createInstance: () => ({ placement: "floating" }),
|