gloomberb 0.19.2 → 0.19.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/README.md +7 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +108 -1
- package/src/account/upgrade-dialog.tsx +4 -23
- package/src/api-client/coalesce.ts +118 -0
- package/src/api-client/credit-boards.ts +13 -0
- package/src/api-client/crypto-etf-flows.ts +6 -0
- package/src/api-client/crypto-markets.ts +40 -0
- package/src/api-client/data.ts +201 -14
- package/src/api-client/equity-screener.ts +10 -0
- package/src/api-client/errors.ts +20 -0
- package/src/api-client/gpu.ts +29 -0
- package/src/api-client/index.ts +5 -2
- package/src/api-client/plan-access.ts +10 -1
- package/src/api-client/plan-rules.ts +35 -0
- package/src/api-client/research-activity.ts +1 -32
- package/src/api-client/types.ts +21 -1
- package/src/app/attention-notice.tsx +94 -0
- package/src/app.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +2 -1
- package/src/capabilities/factories.ts +1 -0
- package/src/cli/ascii-output.ts +42 -0
- package/src/cli/base-converter.ts +10 -2
- package/src/cli/command-options.ts +16 -1
- package/src/cli/commands/crypto-hints.ts +23 -4
- package/src/cli/commands/market.ts +92 -23
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +9 -4
- package/src/cli/commands/search.ts +71 -12
- package/src/cli/commands/system.ts +22 -30
- package/src/cli/commands/ticker.ts +49 -9
- package/src/cli/commands/version.ts +33 -0
- package/src/cli/context.ts +5 -9
- package/src/cli/data-dir.ts +7 -0
- package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
- package/src/cli/desktop-pane-shot-browser.ts +320 -0
- package/src/cli/desktop-pane-shot-routes.ts +26 -1
- package/src/cli/desktop-pane-shot.ts +196 -256
- package/src/cli/early-command.ts +42 -0
- package/src/cli/earnings-calendar.ts +9 -0
- package/src/cli/entry.ts +19 -13
- package/src/cli/errors.ts +12 -2
- package/src/cli/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +154 -4
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +40 -5
- package/src/cli/listing-arg.ts +46 -9
- package/src/cli/options.ts +1 -1
- package/src/cli/output-file.ts +60 -13
- package/src/cli/pane-functions/catalog.ts +14 -3
- package/src/cli/pane-functions/data.ts +1 -1
- package/src/cli/pane-functions/dom.ts +45 -0
- package/src/cli/pane-functions/freshness.ts +46 -9
- package/src/cli/pane-functions/function-help.ts +17 -9
- package/src/cli/pane-functions/headless.ts +22 -14
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +6 -2
- package/src/cli/pane-functions/resolver.ts +27 -5
- package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
- package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
- package/src/cli/pane-functions/screenshot-http.ts +70 -0
- package/src/cli/pane-functions/screenshot-render.ts +19 -5
- package/src/cli/quote-answer.ts +20 -0
- package/src/cli/registry.ts +41 -13
- package/src/cli/result.ts +12 -6
- package/src/cli/ticker-resolution.ts +2 -1
- package/src/cli/types.ts +2 -0
- package/src/components/chart/composite/format.ts +3 -0
- package/src/components/chart-table/header.tsx +1 -1
- package/src/components/chart-table/layout.ts +28 -11
- package/src/components/command-bar/action-runtime.ts +4 -0
- package/src/components/command-bar/commands/direct/index.ts +18 -33
- package/src/components/command-bar/commands/direct/runtime.ts +5 -0
- package/src/components/command-bar/commands/registry.ts +10 -1
- package/src/components/command-bar/routes/root/command-items.ts +9 -4
- package/src/components/command-bar/routes/root/runtime.ts +24 -1
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/header.tsx +8 -3
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +7 -2
- package/src/components/layout/pane/footer/model.ts +40 -5
- package/src/components/market-board/index.tsx +3 -2
- package/src/components/onboarding/desks-step.tsx +12 -4
- package/src/components/onboarding/first-run-workspace.ts +81 -4
- package/src/components/onboarding/onboarding-frame.tsx +2 -1
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +54 -23
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
- package/src/components/onboarding/run-setup-again.ts +24 -0
- package/src/components/onboarding/use-onboarding-progress.ts +33 -15
- package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
- package/src/components/onboarding/wizard-model.ts +57 -3
- package/src/components/onboarding/wizard-positions.ts +1 -1
- package/src/components/ui/delayed-mark.tsx +56 -0
- package/src/components/ui/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +8 -1
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +34 -31
- package/src/data/sqlite/access.ts +19 -8
- package/src/data/sqlite/checkpointer.ts +95 -8
- package/src/data/sqlite/database.ts +40 -7
- package/src/docs/snapshot.json +191 -78
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -2
- package/src/layout/desk-keys.ts +4 -1
- package/src/layout/desks.ts +38 -5
- package/src/layout-marketplace/gallery-desktop.tsx +45 -16
- package/src/market-data/batched-reads.ts +16 -0
- package/src/market-data/coordinator/auxiliary.ts +4 -2
- package/src/market-data/coordinator/index.ts +11 -2
- package/src/market-data/futures-board.ts +1 -1
- package/src/market-data/futures-contract.ts +3 -1
- package/src/market-data/market/index-level.ts +6 -4
- package/src/market-data/published-apac-sessions.ts +6 -0
- package/src/news/aggregator.ts +7 -4
- package/src/news/news-model.ts +11 -0
- package/src/news/story-id.ts +22 -0
- package/src/news/ticker-symbols.ts +17 -1
- package/src/notifications/app-notifier.ts +35 -14
- package/src/plugins/builtin/account-management/form-components.tsx +1 -1
- package/src/plugins/builtin/account-management/index.tsx +2 -3
- package/src/plugins/builtin/account-management/model.ts +1 -1
- package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
- package/src/plugins/builtin/alerts/events.ts +10 -2
- package/src/plugins/builtin/alerts/index.tsx +5 -5
- package/src/plugins/builtin/alerts/research-builder.ts +5 -1
- package/src/plugins/builtin/alerts/research-rules.ts +18 -0
- package/src/plugins/builtin/alerts/targets.ts +4 -1
- package/src/plugins/builtin/attention/headless.ts +1 -1
- package/src/plugins/builtin/attention/index.tsx +2 -4
- package/src/plugins/builtin/attention/listing.ts +1 -1
- package/src/plugins/builtin/attention/series.ts +1 -1
- package/src/plugins/builtin/awards/index.tsx +1 -2
- package/src/plugins/builtin/backtest/client.ts +1 -0
- package/src/plugins/builtin/backtest/index.tsx +1 -2
- package/src/plugins/builtin/backtest/rules.ts +3 -1
- package/src/plugins/builtin/backtest/window.ts +8 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
- package/src/plugins/builtin/broker-manager/index.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +5 -0
- package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
- package/src/plugins/builtin/buildout/company/id.ts +2 -0
- package/src/plugins/builtin/buildout/company/model.ts +247 -0
- package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
- package/src/plugins/builtin/buildout/facilities.ts +309 -0
- package/src/plugins/builtin/buildout/format.ts +9 -0
- package/src/plugins/builtin/buildout/index.tsx +51 -5
- package/src/plugins/builtin/buildout/model/index.ts +62 -10
- package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
- package/src/plugins/builtin/buildout/model/types.ts +11 -2
- package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
- package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
- package/src/plugins/builtin/buildout/table-model.ts +26 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
- package/src/plugins/builtin/cds/headless.ts +10 -2
- package/src/plugins/builtin/cds/model.ts +7 -3
- package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
- package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
- package/src/plugins/builtin/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -2
- package/src/plugins/builtin/chart-composer/headless.ts +88 -21
- package/src/plugins/builtin/chart-composer/index.tsx +2 -3
- package/src/plugins/builtin/chart-composer/pane.tsx +26 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/controller/index.ts +1 -1
- package/src/plugins/builtin/chat/controller/messages.ts +15 -4
- package/src/plugins/builtin/chat/ghost-user.ts +8 -0
- package/src/plugins/builtin/chat/index.tsx +5 -5
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/index.tsx +1 -2
- package/src/plugins/builtin/cloud/plugin.tsx +6 -4
- package/src/plugins/builtin/cloud/team/module.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
- package/src/plugins/builtin/comment-letters/index.tsx +1 -2
- package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
- package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
- package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
- package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
- package/src/plugins/builtin/congress-trades/headless.ts +2 -0
- package/src/plugins/builtin/congress-trades/model.ts +92 -64
- package/src/plugins/builtin/congress-trades/table.tsx +14 -2
- package/src/plugins/builtin/connections/index.ts +2 -3
- package/src/plugins/builtin/cot/index.tsx +1 -2
- package/src/plugins/builtin/cpi/index.tsx +1 -2
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/client.ts +7 -2
- package/src/plugins/builtin/credit-boards/headless.ts +25 -12
- package/src/plugins/builtin/credit-boards/model.ts +29 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- package/src/plugins/builtin/crypto-board/headless.ts +4 -3
- package/src/plugins/builtin/crypto-board/index.tsx +4 -2
- package/src/plugins/builtin/crypto-board/series.ts +2 -0
- package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
- package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
- package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
- package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
- package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
- package/src/plugins/builtin/debt-maturities/model.ts +17 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
- package/src/plugins/builtin/debug/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/client.ts +32 -2
- package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
- package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
- package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
- package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
- package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
- package/src/plugins/builtin/dividend-yield/view.ts +21 -0
- package/src/plugins/builtin/docs/index.tsx +2 -3
- package/src/plugins/builtin/doe/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
- package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
- package/src/plugins/builtin/econ/calendar-model.ts +29 -6
- package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +1 -0
- package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
- package/src/plugins/builtin/equity-screener/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/model.ts +28 -4
- package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
- package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
- package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
- package/src/plugins/builtin/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/cli.ts +44 -0
- package/src/plugins/builtin/fear-greed/index.tsx +5 -53
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
- package/src/plugins/builtin/fixed-income/index.tsx +1 -2
- package/src/plugins/builtin/futures/board-reads.ts +25 -0
- package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
- package/src/plugins/builtin/futures/index.tsx +15 -0
- package/src/plugins/builtin/futures/model.ts +14 -0
- package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
- package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
- package/src/plugins/builtin/futures-curve/headless.ts +6 -1
- package/src/plugins/builtin/futures-curve/index.tsx +1 -2
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +51 -12
- package/src/plugins/builtin/gpu/client.ts +15 -2
- package/src/plugins/builtin/gpu/equities.tsx +5 -2
- package/src/plugins/builtin/gpu/headless.ts +14 -2
- package/src/plugins/builtin/gpu/index.tsx +1 -2
- package/src/plugins/builtin/gpu/model.ts +20 -2
- package/src/plugins/builtin/gpu/pane.tsx +1 -1
- package/src/plugins/builtin/help/index.tsx +1 -1
- package/src/plugins/builtin/holders/index-holders.ts +24 -12
- package/src/plugins/builtin/holders/index.tsx +2 -3
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
- package/src/plugins/builtin/iv-history/index.tsx +1 -2
- package/src/plugins/builtin/jodi/index.tsx +1 -2
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
- package/src/plugins/builtin/layout-manager/index.tsx +9 -8
- package/src/plugins/builtin/macro-day/pane.tsx +4 -2
- package/src/plugins/builtin/market-halts/index.tsx +1 -2
- package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
- package/src/plugins/builtin/market-movers/index.tsx +7 -1
- package/src/plugins/builtin/market-movers/session-body.tsx +4 -1
- package/src/plugins/builtin/market-valuation/index.tsx +1 -2
- package/src/plugins/builtin/members/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/pane.tsx +2 -1
- package/src/plugins/builtin/news/headless.ts +7 -0
- package/src/plugins/builtin/news/wire/index.tsx +1 -1
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
- package/src/plugins/builtin/notes/index.tsx +3 -6
- package/src/plugins/builtin/openfda/index.tsx +1 -2
- package/src/plugins/builtin/options/view.tsx +6 -5
- package/src/plugins/builtin/options-calculator/index.tsx +1 -2
- package/src/plugins/builtin/options-positioning/index.tsx +1 -2
- package/src/plugins/builtin/options-scenario/index.tsx +2 -4
- package/src/plugins/builtin/perps/index.tsx +1 -2
- package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
- package/src/plugins/builtin/plugin-module.ts +16 -2
- package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
- package/src/plugins/builtin/power/index.tsx +1 -2
- package/src/plugins/builtin/rate-path/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/model.ts +16 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
- package/src/plugins/builtin/research/analyst-headless.ts +33 -6
- package/src/plugins/builtin/research/analyst-model.ts +37 -2
- package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
- package/src/plugins/builtin/research/index.tsx +5 -8
- package/src/plugins/builtin/research-search/index.tsx +1 -2
- package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
- package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
- package/src/plugins/builtin/roadmap/index.tsx +2 -3
- package/src/plugins/builtin/roadmap/pane.tsx +49 -2
- package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
- package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
- package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
- package/src/plugins/builtin/sec/filing-content.ts +38 -6
- package/src/plugins/builtin/sec/headless.ts +5 -1
- package/src/plugins/builtin/sec/index.tsx +4 -2
- package/src/plugins/builtin/sec/model.ts +2 -0
- package/src/plugins/builtin/sectors/index.tsx +11 -5
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +60 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
- package/src/plugins/builtin/shared/forming-bars.ts +29 -0
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
- package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/pane.tsx +2 -1
- package/src/plugins/builtin/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- package/src/plugins/builtin/supply-chain/client.ts +22 -1
- package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
- package/src/plugins/builtin/supply-chain/index.tsx +1 -2
- package/src/plugins/builtin/thirteenf/api.ts +116 -13
- package/src/plugins/builtin/thirteenf/data.ts +132 -30
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +20 -11
- package/src/plugins/builtin/thirteenf/types.ts +12 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +17 -4
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/headless.ts +11 -3
- package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
- package/src/plugins/builtin/time-sales/index.tsx +1 -2
- package/src/plugins/builtin/time-sales/pane.tsx +3 -1
- package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
- package/src/plugins/builtin/trace-bonds/model.ts +44 -10
- package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
- package/src/plugins/builtin/treasury-auctions/pane.tsx +2 -2
- package/src/plugins/builtin/vol-surface/client.ts +18 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -2
- package/src/plugins/builtin/volatility/index.tsx +1 -2
- package/src/plugins/builtin/world-indices/footer.ts +6 -1
- package/src/plugins/builtin/world-venue-map/client.ts +10 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -2
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
- package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
- package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
- package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
- package/src/plugins/builtin/yield-curve/curves.ts +16 -2
- package/src/plugins/builtin/yield-curve/headless.ts +3 -3
- package/src/plugins/bundle.ts +52 -2
- package/src/plugins/catalog-browser.ts +2 -2
- package/src/plugins/catalog-cli.ts +20 -0
- package/src/plugins/host-link.ts +13 -1
- package/src/plugins/installer.ts +15 -4
- package/src/plugins/loader.ts +43 -11
- package/src/plugins/peer-imports.ts +201 -0
- package/src/plugins/peer-retry.ts +26 -29
- package/src/plugins/plugin-process.ts +128 -0
- package/src/plugins/redirection-guard.ts +45 -0
- package/src/remote/endpoint.ts +16 -3
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/http-proxy.ts +7 -123
- package/src/renderers/dom/desktop/icons.tsx +2 -0
- package/src/renderers/dom/host/image-surface.tsx +46 -21
- package/src/renderers/dom/styles.css +7 -0
- package/src/renderers/dom/toast-host.tsx +2 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +3 -2
- package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
- package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
- package/src/renderers/electrobun/bun/window/create.ts +4 -2
- package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
- package/src/renderers/electrobun/view/main.tsx +7 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/renderers/opentui/browser-open.ts +10 -4
- package/src/renderers/opentui/image/surface.tsx +1 -1
- package/src/renderers/opentui/start.tsx +12 -6
- package/src/renderers/opentui/toast-host.tsx +2 -1
- package/src/series/registry.ts +18 -0
- package/src/series/standalone.ts +10 -1
- package/src/series/types.ts +7 -0
- package/src/series/validate.ts +11 -2
- package/src/sources/gloomberb-cloud/index.ts +28 -5
- package/src/sources/gloomberb-cloud/news.ts +24 -3
- package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
- package/src/sources/provider-errors.ts +26 -3
- package/src/sources/provider-router/history.ts +5 -0
- package/src/sources/provider-router/index.ts +13 -0
- package/src/sync/core-contributors.ts +32 -0
- package/src/telemetry/attention-counts.ts +64 -6
- package/src/telemetry/attention-setting.ts +33 -0
- package/src/telemetry/crash-reports-node.ts +3 -1
- package/src/telemetry/stall-clocks.ts +93 -0
- package/src/telemetry/stall-reports-node.ts +12 -7
- package/src/telemetry/stall-reports.ts +34 -5
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name.ts +19 -0
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +13 -2
- package/src/tickers/search/index.ts +4 -1
- package/src/tickers/search/types.ts +6 -1
- package/src/time-series/bar-aggregation.ts +7 -1
- package/src/time-series/base-series.ts +3 -3
- package/src/time-series/chart-bounds.ts +1 -1
- package/src/time-series/chart-data.ts +5 -2
- package/src/time-series/chart-seed.ts +1 -1
- package/src/time-series/coin-session-close.ts +211 -0
- package/src/time-series/live-bars.ts +13 -2
- package/src/time-series/live-quotes.ts +1 -1
- package/src/time-series/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +55 -6
- package/src/time-series/resolve.ts +12 -3
- package/src/time-series/series-presentation.ts +1 -1
- package/src/time-series/studies/formula.ts +205 -0
- package/src/time-series/studies/indicators.ts +299 -0
- package/src/time-series/studies/leverage.ts +75 -0
- package/src/time-series/studies/pair.ts +138 -0
- package/src/time-series/studies/resolve.ts +333 -0
- package/src/time-series/studies/shared.ts +141 -0
- package/src/time-series/studies/units.ts +243 -0
- package/src/time-series/studies/vwap.ts +274 -0
- package/src/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +22 -0
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +11 -0
- package/src/ui/toast.tsx +26 -1
- package/src/utils/ascii-text.ts +140 -0
- package/src/utils/cli-output.ts +91 -18
- package/src/utils/concurrency.ts +24 -0
- package/src/utils/exchanges.ts +6 -0
- package/src/utils/external-url.ts +20 -7
- package/src/utils/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/main-thread-stall.ts +75 -3
- package/src/utils/perf-marks.ts +36 -2
- package/src/utils/private-files.ts +141 -0
- package/src/utils/public-address.ts +99 -0
- package/src/account/upgrade-personalization.ts +0 -57
- package/src/account/upgrade-tickers.ts +0 -124
- package/src/api-client/upgrade-experiment.ts +0 -91
- package/src/plugins/builtin/cloud/browser.tsx +0 -3
- package/src/renderers/electrobun/launcher/preload.ts +0 -13
- package/src/renderers/electrobun/launcher/windows-emulation.ts +0 -50
- package/src/time-series/studies.ts +0 -1628
|
@@ -15,6 +15,7 @@ import { isoDateDaysAgo, isoDateToday, isoDateYearsAgo } from "../../../utils/ca
|
|
|
15
15
|
import { abortable, withDeadline } from "../../../utils/async-deadline";
|
|
16
16
|
import { logFailure } from "../../../utils/log-failure";
|
|
17
17
|
import {
|
|
18
|
+
amendmentKind,
|
|
18
19
|
buildBrowserRows,
|
|
19
20
|
filingGapFor,
|
|
20
21
|
estimateFilingQuarterReturn,
|
|
@@ -33,6 +34,7 @@ import type {
|
|
|
33
34
|
FundBrowserRow,
|
|
34
35
|
FundDetailData,
|
|
35
36
|
ThirteenFBrowserTab,
|
|
37
|
+
ThirteenFDetailLoading,
|
|
36
38
|
ThirteenFFilingGap,
|
|
37
39
|
ThirteenFFund,
|
|
38
40
|
ThirteenFHoldingRecord,
|
|
@@ -162,9 +164,12 @@ async function loadBrowserPage(
|
|
|
162
164
|
const pageApiOptions = { ...apiOptions, offset };
|
|
163
165
|
|
|
164
166
|
if (tab === "performance") {
|
|
165
|
-
const topFunds = await listTopThirteenFFunds(quarter, browserLimit, signal, pageApiOptions);
|
|
166
167
|
const quarters = priorQuarters(quarter);
|
|
167
|
-
|
|
168
|
+
// The prior quarters' rankings do not depend on this page, so they are read beside it.
|
|
169
|
+
const [topFunds, history] = await Promise.all([
|
|
170
|
+
listTopThirteenFFunds(quarter, browserLimit, signal, pageApiOptions),
|
|
171
|
+
loadRankingHistory(quarters, signal, apiOptions),
|
|
172
|
+
]);
|
|
168
173
|
const funds: ThirteenFFund[] = topFunds.map((fund) => ({ cik: fund.cik, name: fund.name }));
|
|
169
174
|
const rows = buildBrowserRows({ topFunds, source: "performance" }).map(row => ({ ...row, priorReturns: quarters.map((previous, index) => ({ quarter: previous, value: history[index]?.get(row.cik) ?? null })) }));
|
|
170
175
|
const result: BrowserLoadResult = {
|
|
@@ -183,7 +188,10 @@ async function loadBrowserPage(
|
|
|
183
188
|
|
|
184
189
|
if (tab === "latest") {
|
|
185
190
|
const filings = await listThirteenFFilings(isoDateDaysAgo(21, now), to, latestLimit, signal, pageApiOptions);
|
|
186
|
-
|
|
191
|
+
// An original filing or a restatement is its period's whole report, and an
|
|
192
|
+
// amendment of unknown kind shows no totals either way: only one that adds
|
|
193
|
+
// NEW HOLDINGS needs the fund's earlier filing for its quarter's totals.
|
|
194
|
+
const selected = dedupeLatestForms(filings).filter((form) => amendmentKind(form) === "new-holdings");
|
|
187
195
|
const rows = buildBrowserRows({ latestFilings: filings, source: "latest" });
|
|
188
196
|
const result: BrowserLoadResult = {
|
|
189
197
|
rows,
|
|
@@ -516,12 +524,66 @@ export async function loadFundPerformance(
|
|
|
516
524
|
};
|
|
517
525
|
}
|
|
518
526
|
|
|
527
|
+
/** Rows a fund's two newest reports may hold together for their holdings to be read ahead. */
|
|
528
|
+
const PREFETCH_HOLDINGS_MAX = 2_000;
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Reads ahead what opening a fund asks first, while the cursor rests on its
|
|
532
|
+
* row: its filing list, which every other request waits on, and the holdings
|
|
533
|
+
* of its two newest reports when they hold PREFETCH_HOLDINGS_MAX rows or fewer
|
|
534
|
+
* together. A large filer's holdings wait for Enter. These are loadFundDetail's
|
|
535
|
+
* own requests, so opening the fund reads them from cache or joins them.
|
|
536
|
+
*/
|
|
537
|
+
export async function prefetchFundDetail(cik: string, signal: AbortSignal): Promise<void> {
|
|
538
|
+
const now = new Date();
|
|
539
|
+
const forms = await listThirteenFForms(cik, isoDateYearsAgo(4, now), isoDateToday(now), 100, signal);
|
|
540
|
+
const filings = buildPeriodReports(forms).slice(0, 2).flatMap((report) => report.filings);
|
|
541
|
+
const rows = filings.reduce((sum, form) => sum + (form.tableEntryTotal ?? Infinity), 0);
|
|
542
|
+
if (!filings.length || rows > PREFETCH_HOLDINGS_MAX) return;
|
|
543
|
+
await Promise.all(filings.map((form) => listThirteenFFormHoldings(cik, form.accessionNumber, signal, { entries: form.tableEntryTotal })));
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* One fund read ahead at a time (prefetchFundDetail): resting on another row
|
|
548
|
+
* stops the last one's requests, and `cancel` stops it when the list goes.
|
|
549
|
+
*/
|
|
550
|
+
export function createFundPrefetcher(prefetch: (cik: string, signal: AbortSignal) => Promise<void> = prefetchFundDetail) {
|
|
551
|
+
let current: { cik: string; controller: AbortController } | null = null;
|
|
552
|
+
return {
|
|
553
|
+
prefetch(cik: string) {
|
|
554
|
+
if (current?.cik === cik) return;
|
|
555
|
+
current?.controller.abort();
|
|
556
|
+
const entry = { cik, controller: new AbortController() };
|
|
557
|
+
current = entry;
|
|
558
|
+
void prefetch(cik, entry.controller.signal).catch(() => {
|
|
559
|
+
// Intentional ignore: a fund read ahead that fails is read again, and the failure shown, when it opens.
|
|
560
|
+
}).finally(() => {
|
|
561
|
+
if (current === entry) current = null;
|
|
562
|
+
});
|
|
563
|
+
},
|
|
564
|
+
cancel() {
|
|
565
|
+
current?.controller.abort();
|
|
566
|
+
current = null;
|
|
567
|
+
},
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* A fund's latest report and the one before it, with their holdings. With
|
|
573
|
+
* `onProgress`, the fund as it fills in: the latest report's holdings as
|
|
574
|
+
* soon as they are read (a large filing's first rows before the rest), then
|
|
575
|
+
* the prior quarter they are compared with and EDGAR's filing list, each
|
|
576
|
+
* part still loading named in `loading`. The answer is the whole fund.
|
|
577
|
+
*/
|
|
519
578
|
export async function loadFundDetail(
|
|
520
579
|
cik: string,
|
|
521
580
|
fallbackName: string,
|
|
522
581
|
signal?: AbortSignal,
|
|
523
582
|
/** `filingGap: false` skips EDGAR's filing list, for a load that never shows it (an overlap). */
|
|
524
|
-
options: Pick<Forms13FReadOptions, "forceRefresh" | "onSavedCopy"> & {
|
|
583
|
+
options: Pick<Forms13FReadOptions, "forceRefresh" | "onSavedCopy"> & {
|
|
584
|
+
filingGap?: boolean;
|
|
585
|
+
onProgress?: (detail: FundDetailData) => void;
|
|
586
|
+
} = {},
|
|
525
587
|
): Promise<FundDetailData> {
|
|
526
588
|
const now = new Date();
|
|
527
589
|
const warnings: string[] = [];
|
|
@@ -541,47 +603,87 @@ export async function loadFundDetail(
|
|
|
541
603
|
const previousReport = reports[1];
|
|
542
604
|
const latestForm = latestReport?.filings.at(-1) ?? null;
|
|
543
605
|
const previousForm = previousReport?.filings.at(-1) ?? null;
|
|
544
|
-
|
|
606
|
+
const checkGap = options.filingGap !== false;
|
|
607
|
+
const parts: Pick<FundDetailData, "latestHoldings" | "previousHoldings" | "filingGap"> & { loading: ThirteenFDetailLoading } = {
|
|
608
|
+
latestHoldings: [],
|
|
609
|
+
previousHoldings: [],
|
|
610
|
+
filingGap: null,
|
|
611
|
+
loading: { positions: true, ...(previousReport ? { comparison: true } : {}), ...(checkGap ? { filingGap: true } : {}) },
|
|
612
|
+
};
|
|
613
|
+
const detail = (): FundDetailData => {
|
|
614
|
+
const loading = Object.fromEntries(Object.entries(parts.loading).filter(([, value]) => value)) as ThirteenFDetailLoading;
|
|
615
|
+
return {
|
|
616
|
+
cik: normalizeCik(cik),
|
|
617
|
+
name: latestForm?.companyName || fallbackName,
|
|
618
|
+
forms,
|
|
619
|
+
latestForm,
|
|
620
|
+
previousForm,
|
|
621
|
+
latestHoldings: parts.latestHoldings,
|
|
622
|
+
previousHoldings: parts.previousHoldings,
|
|
623
|
+
latestReport,
|
|
624
|
+
previousReport,
|
|
625
|
+
warnings: [...warnings],
|
|
626
|
+
filingGap: parts.filingGap,
|
|
627
|
+
...(saved.savedAt != null ? { savedAt: saved.savedAt } : {}),
|
|
628
|
+
...(Object.keys(loading).length ? { loading } : {}),
|
|
629
|
+
};
|
|
630
|
+
};
|
|
631
|
+
let failed = false;
|
|
632
|
+
// The fund is shown once its latest holdings, or a large filing's first rows, are in.
|
|
633
|
+
const progress = () => {
|
|
634
|
+
if (!options.onProgress || failed || signal?.aborted || (parts.loading.positions && !parts.latestHoldings.length)) return;
|
|
635
|
+
options.onProgress(detail());
|
|
636
|
+
};
|
|
637
|
+
async function loadReport(report: ThirteenFPeriodReport | undefined, onFirstRows?: (rows: ThirteenFHoldingRecord[]) => void): Promise<ThirteenFHoldingRecord[]> {
|
|
545
638
|
if (!report) return [];
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
639
|
+
// An amended period's filings are read at once: the original and each of its additions.
|
|
640
|
+
const filings = await Promise.all(report.filings.map((form, index) => listThirteenFFormHoldings(cik, form.accessionNumber, signal, {
|
|
641
|
+
...apiOptions,
|
|
642
|
+
entries: form.tableEntryTotal,
|
|
643
|
+
...(index === 0 && onFirstRows ? { onFirstRows } : {}),
|
|
644
|
+
})));
|
|
645
|
+
report.filings.forEach((form, index) => {
|
|
646
|
+
const rows = filings[index]!;
|
|
550
647
|
if (form.tableEntryTotal != null && rows.length !== form.tableEntryTotal) {
|
|
551
648
|
report.complete = false;
|
|
552
649
|
warnings.push(`${report.periodOfReport}: loaded ${rows.length} of ${form.tableEntryTotal} disclosed entries for ${form.accessionNumber}.`);
|
|
553
650
|
}
|
|
554
|
-
}
|
|
651
|
+
});
|
|
555
652
|
if (!report.complete && !warnings.some((warning) => warning.startsWith(report.periodOfReport))) {
|
|
556
653
|
warnings.push(`${report.periodOfReport}: amendment type or original report unavailable; disclosed entries cannot be reconciled into a full report.`);
|
|
557
654
|
}
|
|
558
|
-
return
|
|
655
|
+
return filings.flat();
|
|
559
656
|
}
|
|
560
|
-
|
|
561
|
-
loadReport(latestReport)
|
|
657
|
+
await Promise.all([
|
|
658
|
+
loadReport(latestReport, options.onProgress ? (rows) => {
|
|
659
|
+
if (!parts.loading.positions) return;
|
|
660
|
+
parts.latestHoldings = rows;
|
|
661
|
+
progress();
|
|
662
|
+
} : undefined).then((rows) => {
|
|
663
|
+
parts.latestHoldings = rows;
|
|
664
|
+
parts.loading.positions = false;
|
|
665
|
+
progress();
|
|
666
|
+
}),
|
|
562
667
|
loadReport(previousReport).catch((error) => {
|
|
563
668
|
if (signal?.aborted) throw error;
|
|
564
669
|
if (previousReport) previousReport.complete = false;
|
|
565
670
|
warnings.push(`${previousReport?.periodOfReport ?? "Prior quarter"}: holdings unavailable; comparison unavailable.`);
|
|
566
671
|
return [];
|
|
672
|
+
}).then((rows) => {
|
|
673
|
+
parts.previousHoldings = rows;
|
|
674
|
+
parts.loading.comparison = false;
|
|
675
|
+
progress();
|
|
567
676
|
}),
|
|
568
|
-
options.
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
previousHoldings,
|
|
579
|
-
latestReport,
|
|
580
|
-
previousReport,
|
|
581
|
-
warnings,
|
|
582
|
-
filingGap,
|
|
583
|
-
...(saved.savedAt != null ? { savedAt: saved.savedAt } : {}),
|
|
584
|
-
};
|
|
677
|
+
(checkGap ? loadFilingGap(cik, latestForm?.periodOfReport, signal, now, { forceRefresh: options.forceRefresh }) : Promise.resolve(null)).then((gap) => {
|
|
678
|
+
parts.filingGap = gap;
|
|
679
|
+
parts.loading.filingGap = false;
|
|
680
|
+
progress();
|
|
681
|
+
}),
|
|
682
|
+
]).catch((error: unknown) => {
|
|
683
|
+
failed = true;
|
|
684
|
+
throw error;
|
|
685
|
+
});
|
|
686
|
+
return detail();
|
|
585
687
|
}
|
|
586
688
|
|
|
587
689
|
export async function loadFilingPositions(
|
|
@@ -6,13 +6,11 @@ import {
|
|
|
6
6
|
inferBrowserTabFromQuery,
|
|
7
7
|
isCikQuery,
|
|
8
8
|
} from "./model";
|
|
9
|
-
import { ThirteenFPane } from "./pane";
|
|
10
9
|
import {
|
|
11
10
|
attachThirteenFApiPersistence,
|
|
12
11
|
resetThirteenFApiPersistence,
|
|
13
12
|
} from "./api";
|
|
14
13
|
import { thirteenFHeadless } from "./headless";
|
|
15
|
-
import { ThirteenFTickerPane } from "./signals-pane";
|
|
16
14
|
import { isKnownNonUsListing } from "../../../utils/sec";
|
|
17
15
|
import { isKnownMutualFund } from "../../../tickers/instrument-kind";
|
|
18
16
|
|
|
@@ -32,7 +30,7 @@ export const thirteenFModule: PluginModule = {
|
|
|
32
30
|
id: "thirteenf",
|
|
33
31
|
name: "13F",
|
|
34
32
|
order: 39,
|
|
35
|
-
component: ThirteenFTickerPane,
|
|
33
|
+
get component(): typeof import("./signals-pane").ThirteenFTickerPane { const { ThirteenFTickerPane } = require("./signals-pane"); return ThirteenFTickerPane; },
|
|
36
34
|
instruments: ["equity", "fund"],
|
|
37
35
|
isVisible: ({ ticker, financials }) => !isKnownNonUsListing(ticker) && !isKnownMutualFund(ticker, financials),
|
|
38
36
|
});
|
|
@@ -47,7 +45,7 @@ export const thirteenFModule: PluginModule = {
|
|
|
47
45
|
id: THIRTEENF_PANE_ID,
|
|
48
46
|
name: "13F Funds",
|
|
49
47
|
icon: "13F",
|
|
50
|
-
component: ThirteenFPane,
|
|
48
|
+
get component(): typeof import("./pane").ThirteenFPane { const { ThirteenFPane } = require("./pane"); return ThirteenFPane; },
|
|
51
49
|
defaultPosition: "right",
|
|
52
50
|
defaultMode: "floating",
|
|
53
51
|
defaultFloatingSize: { width: 112, height: 36 },
|
|
@@ -547,6 +547,8 @@ export function estimateFilingQuarterReturn(data: FundDetailData, splits: SplitF
|
|
|
547
547
|
|
|
548
548
|
export function hasComparable13FQuarter(data: FundDetailData): boolean {
|
|
549
549
|
if (data.latestReport?.complete === false || data.previousReport?.complete === false) return false;
|
|
550
|
+
// Until both quarters are in whole, a line one of them lacks is not a buy or a sale.
|
|
551
|
+
if (data.loading?.positions || data.loading?.comparison) return false;
|
|
550
552
|
const current = data.latestForm?.periodOfReport;
|
|
551
553
|
const previous = data.previousForm?.periodOfReport;
|
|
552
554
|
return adjacentQuarterPeriods(current, previous);
|
|
@@ -23,7 +23,7 @@ import { nextHeaderSort } from "../../../utils/sort-values";
|
|
|
23
23
|
import { useAssetData, usePluginPaneState, usePluginTickerActions } from "../../runtime";
|
|
24
24
|
import { useMineTickers } from "../shared/mine-tickers";
|
|
25
25
|
import { usePaneStatusFooter, usePaneStatusLinkFooter } from "../../../components/layout/pane/status-footer";
|
|
26
|
-
import { corporateSplitFactor, loadBrowserRows, loadFilingPositions, loadFundDetail, loadSplitFactors } from "./data";
|
|
26
|
+
import { corporateSplitFactor, createFundPrefetcher, loadBrowserRows, loadFilingPositions, loadFundDetail, loadSplitFactors } from "./data";
|
|
27
27
|
import { FundOverlapView } from "./overlap-pane";
|
|
28
28
|
import { ThirteenFCrowdingPane, ThirteenFTickerHoldingsView } from "./signals-pane";
|
|
29
29
|
import { PaneFooterScope, savedCopyFooterInfo } from "../../../components/layout/pane/footer";
|
|
@@ -196,13 +196,11 @@ function ThirteenFBrowserPane({ focused, width, height, onDetailChange }: PanePr
|
|
|
196
196
|
blurSearch();
|
|
197
197
|
setDetailSeed({ cik: row.cik, name: row.name });
|
|
198
198
|
}, [blurSearch]);
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
});
|
|
205
|
-
}, []);
|
|
199
|
+
// The fund's first requests are read ahead while the cursor rests on its
|
|
200
|
+
// row, one fund at a time, and stopped when the cursor moves on.
|
|
201
|
+
const prefetcher = useMemo(() => createFundPrefetcher(), []);
|
|
202
|
+
useEffect(() => () => prefetcher.cancel(), [prefetcher]);
|
|
203
|
+
const prefetchDetail = useCallback((row: FundBrowserRow) => prefetcher.prefetch(row.cik), [prefetcher]);
|
|
206
204
|
|
|
207
205
|
const browserStatusInfo = useMemo<PaneFooterSegment[]>(() => [
|
|
208
206
|
...(loadingMore ? [{ id: "loading-more", parts: [{ text: "loading more", tone: "muted" as const }] }] : []),
|
|
@@ -372,7 +370,11 @@ export function FundDetailView({
|
|
|
372
370
|
abortRef.current = controller;
|
|
373
371
|
setStatus("loading");
|
|
374
372
|
setError(null);
|
|
375
|
-
void loadFundDetail(seed.cik, seed.name, controller.signal, {
|
|
373
|
+
void loadFundDetail(seed.cik, seed.name, controller.signal, {
|
|
374
|
+
forceRefresh: refresh,
|
|
375
|
+
// A first load shows the fund as it fills in; a refresh keeps what is on screen until it answers whole.
|
|
376
|
+
...(refresh ? {} : { onProgress: (partial: FundDetailData) => { if (abortRef.current === controller) setData(partial); } }),
|
|
377
|
+
})
|
|
376
378
|
.then((nextData) => {
|
|
377
379
|
if (abortRef.current !== controller) return;
|
|
378
380
|
setData(nextData);
|
|
@@ -392,6 +394,8 @@ export function FundDetailView({
|
|
|
392
394
|
.catch((loadError) => {
|
|
393
395
|
if (abortRef.current !== controller) return;
|
|
394
396
|
if (loadError instanceof Error && loadError.name === "AbortError") return;
|
|
397
|
+
// A first load that failed after its first rows shows the error, not a part of the filing as the fund.
|
|
398
|
+
setData((current) => current?.loading ? null : current);
|
|
395
399
|
setError(loadError instanceof Error ? loadError.message : String(loadError));
|
|
396
400
|
setStatus("error");
|
|
397
401
|
});
|
|
@@ -522,7 +526,10 @@ export function FundDetailView({
|
|
|
522
526
|
...(staleFiling ? { tone: "warning" as const } : {}) },
|
|
523
527
|
data && hasComparable13FQuarter(data)
|
|
524
528
|
? { id: "compared", label: "Compared with", value: data.previousForm!.periodOfReport }
|
|
525
|
-
|
|
529
|
+
// The quarter the changes will compare with, dim until both are in.
|
|
530
|
+
: data?.loading && hasComparable13FQuarter({ ...data, loading: undefined })
|
|
531
|
+
? { id: "compared", label: "Compared with", value: data.previousForm!.periodOfReport, tone: "muted" }
|
|
532
|
+
: { id: "compared", label: "Compared with", value: "No prior quarter", tone: "muted" },
|
|
526
533
|
...(data?.latestReport && data.latestReport.filings.length > 1
|
|
527
534
|
? [{ id: "combined", label: "Combined", value: `${data.latestReport.filings.length} filings` }]
|
|
528
535
|
: []),
|
|
@@ -593,7 +600,9 @@ export function FundDetailView({
|
|
|
593
600
|
]}
|
|
594
601
|
meta={data?.name && data.name !== seed.name ? data.name : undefined}
|
|
595
602
|
/>
|
|
596
|
-
{activeTab === "overlap" && data ?
|
|
603
|
+
{activeTab === "overlap" && data ? data.loading?.positions || data.loading?.comparison ? (
|
|
604
|
+
<PaneStatusBody loading align="center" loadingLabel="Loading 13F filing..." />
|
|
605
|
+
) : <FundOverlapView data={data} focused={focused} width={width} /> : activeTab === "filings" ? (
|
|
597
606
|
<DataTableStackView<FundTimelineRow, FundTimelineColumn>
|
|
598
607
|
focused={focused}
|
|
599
608
|
detailOpen={!!openFiling}
|
|
@@ -101,6 +101,18 @@ export interface FundDetailData {
|
|
|
101
101
|
filingGap?: ThirteenFFilingGap | null;
|
|
102
102
|
/** A refresh failed and the oldest copy that answered instead was saved at this time (ms). */
|
|
103
103
|
savedAt?: number;
|
|
104
|
+
/** The parts still loading, while the fund fills in (loadFundDetail's `onProgress`); absent once whole. */
|
|
105
|
+
loading?: ThirteenFDetailLoading;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Parts of a fund's detail still loading. */
|
|
109
|
+
export interface ThirteenFDetailLoading {
|
|
110
|
+
/** The latest report's holdings are a large filing's first rows. */
|
|
111
|
+
positions?: boolean;
|
|
112
|
+
/** The prior quarter's holdings, which the latest are compared with. */
|
|
113
|
+
comparison?: boolean;
|
|
114
|
+
/** EDGAR's filing list, for a fund behind on its filings. */
|
|
115
|
+
filingGap?: boolean;
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
/** A manager a 13F-NT names as reporting its holdings. */
|
|
@@ -131,7 +131,9 @@ function buildHistoricalPriceRows(points: PricePoint[], venue: string | undefine
|
|
|
131
131
|
const sorted = points
|
|
132
132
|
.flatMap((point, sourceIndex) => {
|
|
133
133
|
const date = pricePointDate(point);
|
|
134
|
-
return date ? [{
|
|
134
|
+
return date ? [{
|
|
135
|
+
point: pricePointValues(point), date, sourceIndex, partial: point.partial === true, filled: point.filled === true, contract: point.contract,
|
|
136
|
+
}] : [];
|
|
135
137
|
})
|
|
136
138
|
.sort((left, right) => left.date.getTime() - right.date.getTime());
|
|
137
139
|
const size = historyBarSize(sorted.map((entry) => entry.date.getTime()));
|
|
@@ -142,10 +144,21 @@ function buildHistoricalPriceRows(points: PricePoint[], venue: string | undefine
|
|
|
142
144
|
const times = sorted.map((entry) => entry.date.getTime());
|
|
143
145
|
const interval = size === "intraday" ? null : size;
|
|
144
146
|
const afterGap = historyBarsAfterHole(times, interval);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
+
// A continuous future's bar on another contract than the bar before has no change: two months' prices are no move.
|
|
148
|
+
let held: string | undefined;
|
|
149
|
+
const rolled = sorted.map((entry) => {
|
|
150
|
+
const roll = !!entry.contract && !!held && held !== entry.contract;
|
|
151
|
+
held = entry.contract ?? held;
|
|
152
|
+
return roll;
|
|
153
|
+
});
|
|
154
|
+
const rolls = sorted.flatMap((entry, index) => rolled[index] ? [`${entry.date.toISOString().slice(0, 10)} (${entry.contract})`] : []);
|
|
155
|
+
const gapNotices = [
|
|
156
|
+
historyMissingPeriodsNote(times, interval),
|
|
157
|
+
historyChangeBaseNote(times, interval),
|
|
158
|
+
rolls.length > 0 ? `No CHG at ${rolls.length === 1 ? "the roll" : "each roll"} to a new contract: ${rolls.join(", ")}` : null,
|
|
159
|
+
].filter((notice): notice is string => notice != null);
|
|
147
160
|
const rows = sorted.map((entry, index) => {
|
|
148
|
-
const previous = sorted[index - 1]?.point;
|
|
161
|
+
const previous = rolled[index] ? undefined : sorted[index - 1]?.point;
|
|
149
162
|
const { point, date, sourceIndex, filled } = entry;
|
|
150
163
|
const newest = index === sorted.length - 1;
|
|
151
164
|
const partial = newest && (!!forming || dayToDate);
|
|
@@ -15,6 +15,10 @@ import type { DataProvider } from "../../../../types/data-provider";
|
|
|
15
15
|
import type { PricePoint } from "../../../../types/financials";
|
|
16
16
|
import { isRoundTheClockCoin } from "../../../../utils/crypto-pair";
|
|
17
17
|
import { isCurrencyPairListing } from "../../../../utils/fx-market-hours";
|
|
18
|
+
import { latestRegularSessionClose } from "../../../../market-data/market/freshness";
|
|
19
|
+
import { futuresSessionOpenMinute } from "../../../../market-data/market/trading-sessions";
|
|
20
|
+
import { calendarBarStart } from "../../../../time-series/chart-data";
|
|
21
|
+
import { newestClosedSession, newestSessionClose, withOfficialCloses } from "../../../../time-series/official-close";
|
|
18
22
|
import {
|
|
19
23
|
effectiveReturnResolution,
|
|
20
24
|
endsAtLatestBar,
|
|
@@ -39,6 +43,8 @@ export interface ReturnSeries {
|
|
|
39
43
|
lastSession?: string;
|
|
40
44
|
/** The venue whose sessions the window follows; empty when none is known. */
|
|
41
45
|
venue: string;
|
|
46
|
+
/** The local date of the newest session, closed but without its official close yet: its last bar ends on the last trade. */
|
|
47
|
+
closingPrintPending?: string;
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
export interface ReturnSeriesRequest {
|
|
@@ -66,20 +72,76 @@ function historyErrorMessage(error: unknown, resolution: ManualChartResolution):
|
|
|
66
72
|
/**
|
|
67
73
|
* The venue whose sessions set where the window ends: the one the ticker
|
|
68
74
|
* names, else the listing its quote names, so a bare VTI ends at the NYSE
|
|
69
|
-
* Arca close on a weekend
|
|
75
|
+
* Arca close on a weekend, with the listing's currency when that was read.
|
|
76
|
+
* Coins and currency pairs need none.
|
|
70
77
|
*/
|
|
71
|
-
async function sessionVenue(provider: DataProvider, symbol: string, exchange: string): Promise<string> {
|
|
72
|
-
if (exchange || isRoundTheClockCoin(symbol) || isCurrencyPairListing(symbol, exchange)) return exchange;
|
|
78
|
+
async function sessionVenue(provider: DataProvider, symbol: string, exchange: string): Promise<{ venue: string; currency?: string }> {
|
|
79
|
+
if (exchange || isRoundTheClockCoin(symbol) || isCurrencyPairListing(symbol, exchange)) return { venue: exchange };
|
|
73
80
|
try {
|
|
74
81
|
const metadata = provider.getQuoteMetadata
|
|
75
82
|
? await provider.getQuoteMetadata(symbol, exchange)
|
|
76
83
|
: await provider.getQuote(symbol, exchange);
|
|
77
|
-
return metadata?.listingExchangeName ?? "";
|
|
84
|
+
return { venue: metadata?.listingExchangeName ?? "", ...(metadata?.currency ? { currency: metadata.currency } : {}) };
|
|
78
85
|
} catch (error) {
|
|
79
|
-
return logFailure("ticker-detail.data-panes.returns-history", "Read return session venue failed", "")(error);
|
|
86
|
+
return { venue: logFailure("ticker-detail.data-panes.returns-history", "Read return session venue failed", "")(error) };
|
|
80
87
|
}
|
|
81
88
|
}
|
|
82
89
|
|
|
90
|
+
const DAY_MS = 86_400_000;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Intraday bars with each closed session's last bar on its official close
|
|
94
|
+
* (withOfficialCloses), so every total and the base they are measured from
|
|
95
|
+
* are the closes `history` and CMP give, not the last trade before the
|
|
96
|
+
* closing auction. Earlier sessions take their daily bar's close, dated by
|
|
97
|
+
* the venue's local date; the newest closed session the close
|
|
98
|
+
* newestSessionClose picks from the quote and its daily bar, or the note
|
|
99
|
+
* that its print is to come. Daily and longer bars, futures (which settle
|
|
100
|
+
* rather than close on a print) and listings without sessions are returned
|
|
101
|
+
* as loaded.
|
|
102
|
+
*/
|
|
103
|
+
async function withSessionCloses(
|
|
104
|
+
provider: DataProvider,
|
|
105
|
+
target: { symbol: string; exchange: string; venue: string; currency?: string },
|
|
106
|
+
interval: ManualChartResolution,
|
|
107
|
+
points: PricePoint[],
|
|
108
|
+
now: number,
|
|
109
|
+
context: { cacheMode: "refresh" } | undefined,
|
|
110
|
+
): Promise<{ points: PricePoint[]; closingPrintPending?: string }> {
|
|
111
|
+
const venue = target.venue || undefined;
|
|
112
|
+
if (!isIntradayResolution(interval) || !points.length || futuresSessionOpenMinute(target.symbol, target.venue || target.exchange) != null
|
|
113
|
+
|| !latestRegularSessionClose(venue, now)) return { points };
|
|
114
|
+
const newest = newestClosedSession(points, { venue, now });
|
|
115
|
+
// Whole UTC days, so the same window asks the same read (and its cached copy) all day.
|
|
116
|
+
const first = Math.floor(new Date(points[0]!.date).getTime() / DAY_MS) * DAY_MS - 2 * DAY_MS;
|
|
117
|
+
const end = Math.floor(Math.min(now, new Date(points.at(-1)!.date).getTime() + DAY_MS) / DAY_MS) * DAY_MS + DAY_MS - 1;
|
|
118
|
+
const [daily, quote] = await Promise.all([
|
|
119
|
+
fetchHistoryResult(provider, target.symbol, target.exchange, { kind: "detail", start: new Date(first), end: new Date(end), interval: "1d" }, context)
|
|
120
|
+
.catch(logFailure("ticker-detail.data-panes.returns-history", "Read the daily closes of a return window failed", null)),
|
|
121
|
+
// Only the newest closed session needs the quote's word on its close.
|
|
122
|
+
newest
|
|
123
|
+
? Promise.resolve(provider.getQuote(target.symbol, target.exchange, context))
|
|
124
|
+
.catch(logFailure("ticker-detail.data-panes.returns-history", "Read the official close of a return window failed", null))
|
|
125
|
+
: null,
|
|
126
|
+
]);
|
|
127
|
+
const dailyCloses = new Map<string, number>();
|
|
128
|
+
for (const bar of daily?.points ?? []) {
|
|
129
|
+
const time = new Date(bar.date).getTime();
|
|
130
|
+
if (!bar.partial && Number.isFinite(time) && Number.isFinite(bar.close) && bar.close > 0) {
|
|
131
|
+
dailyCloses.set(calendarBarStart(time, "1d", venue), bar.close);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const closes = new Map([...dailyCloses].filter(([date]) => date !== newest?.date));
|
|
135
|
+
const decided = newest
|
|
136
|
+
? newestSessionClose(quote, { ...newest, venue, currency: target.currency ?? null, dailyClose: dailyCloses.get(newest.date) ?? null })
|
|
137
|
+
: null;
|
|
138
|
+
if (newest && decided && "close" in decided) closes.set(newest.date, decided.close);
|
|
139
|
+
return {
|
|
140
|
+
points: withOfficialCloses(points, closes, { venue, now }),
|
|
141
|
+
...(newest && decided && "pending" in decided ? { closingPrintPending: newest.date } : {}),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
83
145
|
/**
|
|
84
146
|
* The bars of a return window and where it starts and ends, as RETURN, its
|
|
85
147
|
* report and its capture all load them: the window ends now while the
|
|
@@ -91,7 +153,7 @@ export async function loadReturnSeries(provider: DataProvider, request: ReturnSe
|
|
|
91
153
|
if (request.window) return loadReturnWindow(provider, request, request.window);
|
|
92
154
|
const { symbol, exchange, range, support, now } = request;
|
|
93
155
|
const interval = effectiveReturnResolution(range, request.resolution, support);
|
|
94
|
-
const venue = await sessionVenue(provider, symbol, exchange);
|
|
156
|
+
const { venue, currency } = await sessionVenue(provider, symbol, exchange);
|
|
95
157
|
const windowEnd = returnWindowEnd({ symbol, exchange: venue }, now);
|
|
96
158
|
const visibleFrom = returnVisibleFrom(range, interval, windowEnd.end, venue || undefined);
|
|
97
159
|
const start = new Date(visibleFrom - returnLookbackMs(range, interval, support));
|
|
@@ -100,18 +162,24 @@ export async function loadReturnSeries(provider: DataProvider, request: ReturnSe
|
|
|
100
162
|
let result;
|
|
101
163
|
try {
|
|
102
164
|
// Exact bounds first, then the interval's range history, as charts do
|
|
103
|
-
// when a source keeps no bars for the exact window
|
|
104
|
-
|
|
165
|
+
// when a source keeps no bars for the exact window or failed to load it
|
|
166
|
+
// (the range history may answer from a saved copy); the failure stands when that has none either.
|
|
167
|
+
let detailFailure: unknown = null;
|
|
168
|
+
result = await fetchHistoryResult(provider, symbol, exchange, { kind: "detail", start, end, interval }, context)
|
|
169
|
+
.catch((error: unknown) => { detailFailure = error; return null; });
|
|
105
170
|
if (!result?.points.length) {
|
|
106
171
|
result = await fetchHistoryResult(provider, symbol, exchange, { kind: "resolution", range: returnSupportRange(range), resolution: interval }, context) ?? result;
|
|
107
172
|
}
|
|
173
|
+
if (!result?.points.length && detailFailure) throw detailFailure;
|
|
108
174
|
} catch (error) {
|
|
109
175
|
throw historyErrorMessage(error, interval);
|
|
110
176
|
}
|
|
111
177
|
if (!result) throw new Error("Price history unavailable");
|
|
112
178
|
const latest = latestBarTime(result.points);
|
|
179
|
+
const closed = await withSessionCloses(provider, { symbol, exchange, venue, currency }, interval, result.points, now, context);
|
|
113
180
|
return {
|
|
114
|
-
points:
|
|
181
|
+
points: closed.points,
|
|
182
|
+
...(closed.closingPrintPending ? { closingPrintPending: closed.closingPrintPending } : {}),
|
|
115
183
|
// A 1W to 5Y window's rows start after its base close, found among the bars by their session days.
|
|
116
184
|
visibleFrom: endsAtLatestBar(range) && latest !== null && latest < windowEnd.end
|
|
117
185
|
? returnVisibleFrom(range, interval, latest, venue || undefined)
|
|
@@ -142,7 +210,7 @@ async function loadReturnWindow(provider: DataProvider, request: ReturnSeriesReq
|
|
|
142
210
|
const step = CHART_RESOLUTION_STEP_MS[request.resolution];
|
|
143
211
|
let interval = available.includes(request.resolution) ? request.resolution
|
|
144
212
|
: available.find((candidate) => CHART_RESOLUTION_STEP_MS[candidate] >= step) ?? available.at(-1) ?? "1d";
|
|
145
|
-
const venue = await sessionVenue(provider, symbol, exchange);
|
|
213
|
+
const { venue, currency } = await sessionVenue(provider, symbol, exchange);
|
|
146
214
|
const context = request.forceRefresh ? { cacheMode: "refresh" as const } : undefined;
|
|
147
215
|
const load = async (resolution: ManualChartResolution) => {
|
|
148
216
|
// A daily or longer bar is stamped at midnight UTC, so the window takes every bar from its first date's day, week or month on.
|
|
@@ -167,8 +235,10 @@ async function loadReturnWindow(provider: DataProvider, request: ReturnSeriesReq
|
|
|
167
235
|
}
|
|
168
236
|
}
|
|
169
237
|
if (!loaded?.result) throw new Error("Price history unavailable");
|
|
238
|
+
const closed = await withSessionCloses(provider, { symbol, exchange, venue, currency }, interval, loaded.result.points, now, context);
|
|
170
239
|
return {
|
|
171
|
-
points:
|
|
240
|
+
points: closed.points,
|
|
241
|
+
...(closed.closingPrintPending ? { closingPrintPending: closed.closingPrintPending } : {}),
|
|
172
242
|
visibleFrom: loaded.visibleFrom,
|
|
173
243
|
visibleTo: Math.min(last, now),
|
|
174
244
|
...(loaded.result.coverageStart ? { coverageStart: loaded.result.coverageStart } : {}),
|
|
@@ -52,6 +52,7 @@ import { DAY_SO_FAR_TEXT, withSessionBar } from "../../../time-series/session-ba
|
|
|
52
52
|
import { buildReturnRows, closedMarketNote, isTodayReturnRow, returnBaseCloseDay, type ReturnRange } from "./data-panes/returns-model";
|
|
53
53
|
import { periodBaseText } from "../../../market-data/market/period-return";
|
|
54
54
|
import { loadReturnSeries } from "./data-panes/returns-history";
|
|
55
|
+
import { CLOSING_PRINT_PENDING_TEXT } from "../../../time-series/official-close";
|
|
55
56
|
import {
|
|
56
57
|
closedHistoryBarDay, formingHistoryBar, HISTORY_DEFAULT_RANGE, historyBarDate, historyBarSize, historyBarSizeWord, historyDateHeader, isDailyOrLongerBar,
|
|
57
58
|
type HistoryBarSize,
|
|
@@ -596,7 +597,10 @@ export const returnsHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
596
597
|
...figures,
|
|
597
598
|
...forming || dayToDate ? [{ key: "partial", header: "Partial", format: (value: unknown) => value === true ? "true" : "" }] : [],
|
|
598
599
|
];
|
|
599
|
-
|
|
600
|
+
// A narrow table drops the dollar moves, the interval's first, rather than putting Total % on a line under every row.
|
|
601
|
+
const textFigures = figures.map((column) => column.key === "intervalChange" ? { ...column, optional: true, dropPriority: 2 }
|
|
602
|
+
: column.key === "totalChange" ? { ...column, optional: true, dropPriority: 1 } : column);
|
|
603
|
+
const textColumns: HeadlessPaneColumn[] = [timeColumn(true), ...textFigures];
|
|
600
604
|
// An interval the range is too long for is served in the nearest longer bar; say so rather than switch silently.
|
|
601
605
|
const notices = [
|
|
602
606
|
maxDrawdownLine,
|
|
@@ -619,8 +623,12 @@ export const returnsHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
619
623
|
oldest: null,
|
|
620
624
|
...(forming ? { periodToDate: forming } : closedDay ? { asOf: closedDay } : {}),
|
|
621
625
|
...(series.venue ? { cadenceExchange: series.venue } : {}),
|
|
622
|
-
// The window ends at the last session while the market is shut, which the status line says
|
|
623
|
-
|
|
626
|
+
// The window ends at the last session while the market is shut, which the status line says, and whether its
|
|
627
|
+
// last bar ends on the official close yet.
|
|
628
|
+
...(series.lastSession || series.closingPrintPending ? { note: [
|
|
629
|
+
series.lastSession ? closedMarketNote(series.lastSession, series.visibleTo) : null,
|
|
630
|
+
series.closingPrintPending ? CLOSING_PRINT_PENDING_TEXT : null,
|
|
631
|
+
].filter(Boolean).join(", ") } : {}),
|
|
624
632
|
...(dayToDate ? { basis: "daily bars", note: `the newest bar is ${DAY_SO_FAR_TEXT}` } : {}),
|
|
625
633
|
},
|
|
626
634
|
metadata: {
|