gloomberb 0.17.4 → 0.17.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -1
- package/patches/electrobun@1.18.1.patch +80 -0
- package/src/api-client/chat.ts +9 -0
- package/src/api-client/commodity-balances.ts +77 -0
- package/src/api-client/data.ts +35 -3
- package/src/api-client/earnings.ts +10 -0
- package/src/api-client/index.ts +5 -0
- package/src/api-client/insider-signals.ts +75 -0
- package/src/api-client/market-discovery.ts +1 -1
- package/src/api-client/normalizers.ts +1 -0
- package/src/api-client/paths.ts +9 -0
- package/src/api-client/research-activity.ts +10 -3
- package/src/api-client/socket.ts +10 -0
- package/src/api-client/types.ts +25 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +2 -1
- package/src/app/runtime/broker-import.ts +23 -21
- package/src/app/runtime/plugin-bindings.ts +13 -7
- package/src/app/runtime/update.ts +64 -4
- package/src/app.tsx +2 -0
- package/src/architecture/core-builtin-allowlist.json +2 -10
- package/src/ask-gloom/pane-context.ts +11 -1
- package/src/brokers/broker-portfolio-sync.ts +66 -0
- package/src/brokers/sync-broker-instance.ts +75 -41
- package/src/cli/commands/market.ts +101 -34
- package/src/cli/commands/options-chain.ts +20 -6
- package/src/cli/commands/overview.ts +54 -13
- package/src/cli/commands/search.ts +1 -1
- package/src/cli/commands/system.ts +19 -1
- package/src/cli/commands/ticker.ts +24 -4
- package/src/cli/context.ts +13 -0
- package/src/cli/errors.ts +15 -2
- package/src/cli/fx-pair.ts +77 -0
- package/src/cli/glossary.ts +34 -4
- package/src/cli/history-rows.ts +25 -11
- package/src/cli/index.ts +30 -12
- package/src/cli/listing-arg.ts +61 -138
- package/src/cli/listing-suggestion.ts +3 -21
- package/src/cli/pane-functions/catalog.ts +50 -0
- package/src/cli/pane-functions/freshness.ts +44 -9
- package/src/cli/pane-functions/function-help.ts +9 -9
- package/src/cli/pane-functions/headless.ts +19 -7
- package/src/cli/pane-functions/index.ts +63 -5
- package/src/cli/pane-functions/market-session.ts +18 -3
- package/src/cli/pane-functions/resolver.ts +29 -8
- package/src/cli/pane-functions/screenshot.ts +2 -0
- package/src/cli/report-tables.ts +11 -4
- package/src/cli/result.ts +2 -0
- package/src/cli/series-search.ts +6 -1
- package/src/cli/ticker-resolution.ts +1 -2
- package/src/cli/unknown-command.ts +18 -0
- package/src/components/chart/composite/composite-chart.tsx +80 -19
- package/src/components/chart/composite/format.ts +17 -0
- package/src/components/chart/composite/legend.tsx +26 -15
- package/src/components/chart/composite/panel-labels.ts +38 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +25 -7
- package/src/components/chart/composite/price-axis-labels.tsx +16 -3
- package/src/components/chart/composite/scene.ts +60 -2
- package/src/components/chart/composite/series-axis-tags.tsx +67 -0
- package/src/components/chart/composite/tools.ts +2 -2
- package/src/components/chart/composite/types.ts +28 -0
- package/src/components/chart/core/view-reset.ts +46 -0
- package/src/components/command-bar/commands/collection.ts +1 -1
- package/src/components/command-bar/commands/direct/index.ts +48 -1
- package/src/components/command-bar/commands/registry.ts +8 -0
- package/src/components/command-bar/list/model.ts +7 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/panel/prompt-binding.ts +8 -0
- package/src/components/command-bar/routes/root/command-items.ts +17 -2
- package/src/components/command-bar/routes/root/pick-prefetch.ts +38 -0
- package/src/components/command-bar/routes/root/provider-search.ts +6 -0
- package/src/components/command-bar/routes/root/runtime.ts +3 -0
- package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -1
- package/src/components/command-bar/routes/root/shortcut-items.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/actions.ts +34 -0
- package/src/components/command-bar/surface/index.tsx +7 -1
- package/src/components/command-bar/workflow/collection-actions.ts +6 -5
- package/src/components/command-bar/workflow/ops.ts +8 -5
- package/src/components/command-bar/workflow/pane-reuse.ts +3 -2
- package/src/components/command-bar/workflow/tickers.ts +1 -1
- package/src/components/form-modal/confirm.tsx +1 -0
- package/src/components/form-modal/request.ts +2 -0
- package/src/components/layout/detached-pane-shell.tsx +6 -1
- package/src/components/layout/pane/content.tsx +11 -8
- package/src/components/layout/pane/sidebar.tsx +24 -11
- package/src/components/layout/pane/ticker-fate-gate.tsx +56 -0
- package/src/components/layout/shell/index.tsx +5 -1
- package/src/components/layout/shell/menu.ts +8 -0
- package/src/components/layout/shell/pane/capture.ts +29 -12
- package/src/components/onboarding/wizard-model.ts +1 -1
- package/src/components/onboarding/wizard-positions.ts +18 -10
- package/src/components/ticker/badge/format.ts +64 -15
- package/src/components/ticker/badge/index.tsx +32 -11
- package/src/components/ticker/badge/list.tsx +4 -3
- package/src/components/ticker/badge/text.tsx +2 -2
- package/src/components/ticker/list-table-view.tsx +8 -1
- package/src/components/ui/confirm-dialog.tsx +30 -4
- package/src/components/ui/data-table/opentui/index.tsx +61 -20
- package/src/components/ui/data-table/opentui/model.ts +104 -0
- package/src/components/ui/fields.tsx +5 -0
- package/src/components/ui/inline-quick-add.tsx +4 -1
- package/src/components/ui/query-bar-keys.tsx +29 -5
- package/src/components/ui/query-bar.tsx +29 -4
- package/src/components/ui/windowed-list.tsx +27 -0
- package/src/data/config/store/normalize.ts +8 -0
- package/src/docs/snapshot.json +248 -5
- package/src/i18n/es.ts +3 -1
- package/src/i18n/ja.ts +3 -1
- package/src/i18n/ko.ts +3 -1
- package/src/i18n/zh-cn.ts +3 -1
- package/src/i18n/zh-tw.ts +3 -1
- package/src/layout-marketplace/cloud.ts +16 -5
- package/src/layout-marketplace/gallery.tsx +4 -4
- package/src/layout-marketplace/linked-sync.ts +9 -3
- package/src/layout-marketplace/linked.ts +24 -3
- package/src/layout-marketplace/model.ts +39 -4
- package/src/market-data/coordinator/events.ts +5 -1
- package/src/market-data/coordinator/index.ts +38 -1
- package/src/market-data/coordinator/quotes.ts +4 -0
- package/src/market-data/futures-contract.ts +53 -0
- package/src/market-data/hooks.tsx +225 -30
- package/src/market-data/market/bar-period.ts +71 -0
- package/src/market-data/market/format.ts +14 -2
- package/src/market-data/market/freshness.ts +28 -1
- package/src/market-data/market/status.ts +152 -13
- package/src/market-data/model-dividend-yield.ts +76 -0
- package/src/market-data/quotes/freshness.ts +30 -1
- package/src/{plugins/builtin/portfolio-list → market-data/quotes}/live-valuation.ts +6 -6
- package/src/market-data/quotes/regular-session-valuation.ts +32 -1
- package/src/plugins/builtin/analytics/metrics.ts +8 -4
- package/src/plugins/builtin/analytics/risk-headless.ts +12 -5
- package/src/plugins/builtin/analytics/risk-model.ts +74 -15
- package/src/plugins/builtin/analytics/risk-pane.tsx +6 -6
- package/src/plugins/builtin/broker-manager/detail.tsx +2 -2
- package/src/plugins/builtin/broker-manager/model.ts +2 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +0 -1
- package/src/plugins/builtin/chart-composer/editor/controller.ts +14 -10
- package/src/plugins/builtin/chart-composer/formula.ts +233 -0
- package/src/plugins/builtin/chart-composer/headless.ts +27 -2
- package/src/plugins/builtin/chart-composer/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/pane.tsx +41 -10
- package/src/plugins/builtin/chart-composer/presets.ts +91 -2
- package/src/plugins/builtin/chart-composer/quick-add.tsx +9 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +44 -11
- package/src/plugins/builtin/chart-composer/series-expression.ts +1 -1
- package/src/plugins/builtin/chart-composer/settings.ts +16 -4
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/content/channel-navigation.ts +17 -6
- package/src/plugins/builtin/chat/content/footer.tsx +5 -2
- package/src/plugins/builtin/chat/content/index.tsx +12 -5
- package/src/plugins/builtin/chat/content/mentions.ts +50 -21
- package/src/plugins/builtin/chat/content/scroll.ts +12 -5
- package/src/plugins/builtin/chat/content/snapshot.ts +11 -0
- package/src/plugins/builtin/chat/content/transcript.tsx +48 -37
- package/src/plugins/builtin/chat/content/whole-message-copy.ts +44 -2
- package/src/plugins/builtin/chat/controller/index.ts +88 -17
- package/src/plugins/builtin/chat/controller/mention-candidates.ts +104 -0
- package/src/plugins/builtin/chat/controller/messages.ts +14 -2
- package/src/plugins/builtin/chat/controller/realtime.ts +9 -0
- package/src/plugins/builtin/chat/controller/state.ts +25 -0
- package/src/plugins/builtin/chat/controller/unread-people.ts +240 -0
- package/src/plugins/builtin/chat/controller/view.ts +4 -0
- package/src/plugins/builtin/chat/conversation-recency.ts +73 -0
- package/src/plugins/builtin/chat/conversations.ts +12 -5
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/layout.ts +23 -5
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
- package/src/plugins/builtin/chat/share/flow.tsx +18 -9
- package/src/plugins/builtin/chat/sidebar-rows.ts +18 -5
- package/src/plugins/builtin/chat/sidebar.tsx +6 -1
- package/src/plugins/builtin/chat/unread-badge.ts +38 -7
- package/src/plugins/builtin/cloud/askg/manifest.ts +4 -3
- package/src/plugins/builtin/cloud/askg/pane-attachment.ts +2 -3
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +16 -5
- package/src/plugins/builtin/commodities-energy/index.tsx +23 -4
- package/src/plugins/builtin/commodity-balances/client.ts +51 -193
- package/src/plugins/builtin/commodity-balances/headless.ts +10 -3
- package/src/plugins/builtin/commodity-balances/model.ts +5 -180
- package/src/plugins/builtin/commodity-balances/pane.tsx +3 -0
- package/src/plugins/builtin/custom-view/index.tsx +1 -1
- package/src/plugins/builtin/custom-view/loader.ts +57 -7
- package/src/plugins/builtin/custom-view/sources.ts +146 -0
- package/src/plugins/builtin/custom-view/view-spec.ts +37 -1
- package/src/plugins/builtin/earnings/board-model.ts +33 -3
- package/src/plugins/builtin/earnings/board-table.ts +59 -14
- package/src/plugins/builtin/earnings/board.tsx +36 -3
- package/src/plugins/builtin/earnings/headless.ts +24 -5
- package/src/plugins/builtin/earnings/history-model.ts +9 -1
- package/src/plugins/builtin/earnings/history-table.ts +16 -5
- package/src/plugins/builtin/earnings/history.tsx +40 -10
- package/src/plugins/builtin/earnings/index.tsx +3 -1
- package/src/plugins/builtin/earnings/no-earnings.ts +52 -0
- package/src/plugins/builtin/econ-statistics/detail.tsx +4 -1
- package/src/plugins/builtin/econ-statistics/headless.ts +5 -0
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +4 -0
- package/src/plugins/builtin/futures/model.ts +5 -4
- package/src/plugins/builtin/futures/series.ts +38 -1
- package/src/plugins/builtin/futures-curve/client.ts +4 -4
- package/src/plugins/builtin/futures-curve/headless.ts +33 -8
- package/src/plugins/builtin/futures-curve/index.tsx +4 -4
- package/src/plugins/builtin/futures-curve/model.ts +109 -6
- package/src/plugins/builtin/futures-curve/pane.tsx +24 -13
- package/src/plugins/builtin/insider/headless.ts +74 -4
- package/src/plugins/builtin/insider/index.tsx +99 -5
- package/src/plugins/builtin/insider/signals-client.ts +69 -0
- package/src/plugins/builtin/insider/signals-model.ts +146 -0
- package/src/plugins/builtin/insider/signals.tsx +176 -0
- package/src/plugins/builtin/iv-history/client.ts +41 -2
- package/src/plugins/builtin/iv-history/headless.ts +24 -16
- package/src/plugins/builtin/iv-history/screen-pane.tsx +25 -7
- package/src/plugins/builtin/jodi/client.ts +42 -205
- package/src/plugins/builtin/jodi/headless.ts +13 -4
- package/src/plugins/builtin/jodi/model.ts +18 -43
- package/src/plugins/builtin/jodi/pane.tsx +5 -0
- package/src/plugins/builtin/kelly-sizer/headless.ts +1 -1
- package/src/plugins/builtin/kelly-sizer/history.ts +5 -3
- package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
- package/src/plugins/builtin/macro-day/release-table.ts +3 -3
- package/src/plugins/builtin/market-movers/client.ts +32 -6
- package/src/plugins/builtin/market-movers/headless.ts +7 -2
- package/src/plugins/builtin/market-movers/model.ts +64 -3
- package/src/plugins/builtin/market-movers/screener.ts +20 -9
- package/src/plugins/builtin/market-valuation/detail.tsx +7 -2
- package/src/plugins/builtin/market-valuation/headless.ts +17 -4
- package/src/plugins/builtin/market-valuation/pane.tsx +7 -3
- package/src/plugins/builtin/market-valuation/view.ts +10 -0
- package/src/plugins/builtin/mna/model.ts +2 -1
- package/src/plugins/builtin/options-positioning/client.ts +10 -0
- package/src/plugins/builtin/options-positioning/headless.ts +88 -13
- package/src/plugins/builtin/options-positioning/index.tsx +2 -2
- package/src/plugins/builtin/options-positioning/model.ts +47 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +55 -26
- package/src/plugins/builtin/options-scenario/charts.tsx +13 -4
- package/src/plugins/builtin/options-scenario/client.ts +111 -17
- package/src/plugins/builtin/options-scenario/collar.ts +141 -0
- package/src/plugins/builtin/options-scenario/csp.ts +1 -1
- package/src/plugins/builtin/options-scenario/freshness.ts +27 -0
- package/src/plugins/builtin/options-scenario/headless.ts +28 -12
- package/src/plugins/builtin/options-scenario/index.tsx +1 -1
- package/src/plugins/builtin/options-scenario/model.ts +55 -5
- package/src/plugins/builtin/options-scenario/pane.tsx +24 -5
- package/src/plugins/builtin/perps/client.ts +77 -6
- package/src/plugins/builtin/perps/headless.ts +25 -14
- package/src/plugins/builtin/perps/index.tsx +1 -1
- package/src/plugins/builtin/perps/model.ts +32 -0
- package/src/plugins/builtin/perps/pane.tsx +5 -4
- package/src/plugins/builtin/portfolio-list/allocation.ts +9 -4
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +39 -20
- package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +15 -7
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +1 -1
- package/src/plugins/builtin/portfolio-list/column-values.ts +4 -3
- package/src/plugins/builtin/portfolio-list/command-bar.ts +1 -1
- package/src/plugins/builtin/portfolio-list/headless.ts +10 -3
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +36 -4
- package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +1 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +35 -6
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +2 -1
- package/src/plugins/builtin/portfolio-list/settings.ts +22 -0
- package/src/plugins/builtin/portfolio-list/since-last-seen.ts +197 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +14 -2
- package/src/plugins/builtin/rate-path/model.ts +1 -1
- package/src/plugins/builtin/rate-path/pane.tsx +2 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +14 -5
- package/src/plugins/builtin/research/event-model.ts +41 -49
- package/src/plugins/builtin/research/event-sources.ts +1 -1
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +6 -0
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
- package/src/plugins/builtin/reverse-dcf/headless.ts +25 -9
- package/src/plugins/builtin/reverse-dcf/index.tsx +4 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +39 -5
- package/src/plugins/builtin/reverse-dcf/pane.tsx +16 -6
- package/src/plugins/builtin/sectors/client.ts +46 -19
- package/src/plugins/builtin/sectors/sector-model.ts +22 -10
- package/src/plugins/builtin/shared/earnings-report.ts +48 -1
- package/src/plugins/builtin/shared/history-span.ts +55 -0
- package/src/plugins/builtin/shared/report-freshness.ts +20 -11
- package/src/plugins/builtin/shared/series-detail-chart.tsx +5 -2
- package/src/plugins/builtin/shared/series-list-detail.tsx +7 -3
- package/src/plugins/builtin/shared/ticker-surface.ts +16 -2
- package/src/plugins/builtin/shared/use-quote-board.ts +12 -5
- package/src/plugins/builtin/themes/headless.ts +15 -2
- package/src/plugins/builtin/thirteenf/api.ts +60 -43
- package/src/plugins/builtin/thirteenf/data.ts +76 -35
- package/src/plugins/builtin/thirteenf/headless.ts +2 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +10 -2
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +85 -17
- package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +92 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +26 -8
- package/src/plugins/builtin/ticker-detail/headless-schema.ts +4 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +95 -22
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +17 -3
- package/src/plugins/builtin/yield-curve/curves.ts +15 -1
- package/src/plugins/builtin/yield-curve/headless.ts +17 -7
- package/src/plugins/builtin/yield-curve/history.ts +2 -0
- package/src/plugins/builtin/yield-curve/index.tsx +23 -18
- package/src/plugins/builtin/yield-curve/series.ts +19 -9
- package/src/plugins/builtin/yield-curve/treasury-data.ts +2 -0
- package/src/plugins/builtin/yield-curve/world.ts +4 -0
- package/src/plugins/registry/host-actions.ts +3 -0
- package/src/{plugins/builtin/portfolio-list → portfolio}/mutations.ts +6 -6
- package/src/portfolio/valuation-basis.ts +4 -21
- package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
- package/src/remote/controller.ts +27 -7
- package/src/remote/schema.ts +25 -9
- package/src/renderers/browser/app-services.ts +7 -1
- package/src/renderers/browser/config-host.ts +55 -2
- package/src/renderers/browser/first-screen.ts +193 -0
- package/src/renderers/browser/main.tsx +59 -7
- package/src/renderers/cloudflare/http-proxy.ts +21 -13
- package/src/renderers/dom/desktop/controls.tsx +2 -0
- package/src/renderers/dom/desktop/query-bar.tsx +1 -1
- package/src/renderers/dom/dom-ui-host.tsx +2 -0
- package/src/renderers/dom/windowed-list.tsx +201 -0
- package/src/renderers/dom/windowed-rows.ts +65 -0
- package/src/renderers/electrobun/bun/application-menu/index.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +3 -7
- package/src/renderers/electrobun/bun/desktop/http-stream.ts +21 -6
- package/src/renderers/electrobun/bun/desktop/update.ts +132 -14
- package/src/renderers/electrobun/shared/protocol.ts +7 -4
- package/src/renderers/electrobun/view/http-fetch.ts +12 -1
- package/src/renderers/electrobun/view/update-host.ts +2 -2
- package/src/shares/bug-report-dialog.tsx +162 -0
- package/src/shares/bug-report-flow.tsx +84 -0
- package/src/shares/bug-report.ts +42 -0
- package/src/shares/capture-flow.ts +19 -0
- package/src/shares/capture-preview.tsx +129 -0
- package/src/shares/chat.ts +10 -1
- package/src/shares/dialog.tsx +16 -91
- package/src/shares/flow.tsx +12 -16
- package/src/shares/personal-pane.ts +35 -1
- package/src/sources/listing-symbols.ts +8 -4
- package/src/state/app/context/index.tsx +13 -0
- package/src/state/app/tab-cursor.ts +85 -0
- package/src/sync/core-contributors.ts +206 -59
- package/src/telemetry/usage-counts.ts +16 -0
- package/src/test-support/config-store.ts +31 -0
- package/src/test-support/moved-symbols.ts +64 -0
- package/src/theme/colors.ts +30 -0
- package/src/tickers/listing-fate.ts +242 -0
- package/src/tickers/open-target.ts +61 -11
- package/src/tickers/search/index.ts +45 -3
- package/src/tickers/search/ranking.ts +19 -2
- package/src/tickers/search/types.ts +3 -0
- package/src/tickers/search/upsert.ts +22 -7
- package/src/tickers/ticker-changes.ts +26 -9
- package/src/time-series/bar-spacing.ts +19 -0
- package/src/time-series/chart-data.ts +37 -13
- package/src/time-series/field-catalog.ts +5 -2
- package/src/time-series/forward-valuation.ts +10 -7
- package/src/time-series/fundamentals.ts +49 -11
- package/src/time-series/hooks.ts +2 -0
- package/src/time-series/live-bars.ts +3 -1
- package/src/time-series/parsed-history-cache.ts +2 -0
- package/src/time-series/price-units.ts +69 -0
- package/src/time-series/resolve.ts +71 -11
- package/src/time-series/session-bar.ts +138 -0
- package/src/time-series/spec.ts +20 -3
- package/src/time-series/studies.ts +211 -12
- package/src/time-series/transforms.ts +2 -0
- package/src/time-series/types.ts +11 -1
- package/src/time-series/valuation-price.ts +13 -0
- package/src/types/config.ts +24 -1
- package/src/types/financials.ts +2 -0
- package/src/types/headless.ts +27 -0
- package/src/types/plugin.ts +7 -0
- package/src/ui/host.tsx +5 -0
- package/src/updater/channel.ts +51 -0
- package/src/updater/github-releases.ts +85 -16
- package/src/updater/index.ts +128 -17
- package/src/utils/asset-category.ts +20 -0
- package/src/utils/datetime-format.ts +7 -1
- package/src/utils/decimal-comma.ts +83 -3
- package/src/utils/fundamentals.ts +7 -2
- package/src/utils/http-proxy-response.ts +66 -9
- package/src/utils/plugin-proxy-hosts.json +1 -5
- package/src/utils/semver.ts +47 -2
- package/src/utils/typo-distance.ts +22 -0
- package/src/plugins/builtin/shared/zip-text.ts +0 -46
|
@@ -58,6 +58,37 @@ async function loadReportsForFunds(
|
|
|
58
58
|
return { reports, ...(failures.length ? { warning: failures.join(" ") } : {}) };
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
function copyReportFields(rows: FundBrowserRow[], nextRows: FundBrowserRow[]) {
|
|
62
|
+
const byId = new Map(nextRows.map((row) => [row.id, row]));
|
|
63
|
+
for (const row of rows) {
|
|
64
|
+
const next = byId.get(row.id);
|
|
65
|
+
if (!next) continue;
|
|
66
|
+
row.name = next.name;
|
|
67
|
+
row.periodOfReport = next.periodOfReport;
|
|
68
|
+
row.filedAsOfDate = next.filedAsOfDate;
|
|
69
|
+
row.tableValueTotal = next.tableValueTotal;
|
|
70
|
+
row.tableEntryTotal = next.tableEntryTotal;
|
|
71
|
+
if ("estQuarterReturn" in next) row.estQuarterReturn = next.estQuarterReturn;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function fillReports(
|
|
76
|
+
result: BrowserLoadResult,
|
|
77
|
+
rows: FundBrowserRow[],
|
|
78
|
+
pending: Promise<{ reports: Map<string, ThirteenFPeriodReport>; warning?: string }>,
|
|
79
|
+
rebuild: (reports: Map<string, ThirteenFPeriodReport>) => FundBrowserRow[],
|
|
80
|
+
signal?: AbortSignal,
|
|
81
|
+
) {
|
|
82
|
+
result.reportsLoaded = pending.then(({ reports, warning }) => {
|
|
83
|
+
if (signal?.aborted) return;
|
|
84
|
+
copyReportFields(rows, rebuild(reports));
|
|
85
|
+
if (warning) result.warning = [result.warning, warning].filter(Boolean).join(" ");
|
|
86
|
+
}).catch((error: unknown) => {
|
|
87
|
+
if (signal?.aborted) return;
|
|
88
|
+
throw error;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
61
92
|
export interface BrowserLoadResult {
|
|
62
93
|
rows: FundBrowserRow[];
|
|
63
94
|
period?: string;
|
|
@@ -65,6 +96,7 @@ export interface BrowserLoadResult {
|
|
|
65
96
|
warning?: string;
|
|
66
97
|
hasMore?: boolean;
|
|
67
98
|
nextOffset?: number;
|
|
99
|
+
reportsLoaded?: Promise<void>;
|
|
68
100
|
}
|
|
69
101
|
|
|
70
102
|
export async function loadBrowserRows(
|
|
@@ -75,7 +107,8 @@ export async function loadBrowserRows(
|
|
|
75
107
|
): Promise<BrowserLoadResult> {
|
|
76
108
|
const warnings: string[] = [];
|
|
77
109
|
const result = await loadBrowserPage(tab, query, signal, { ...options, onWarning: warning => warnings.push(warning) });
|
|
78
|
-
|
|
110
|
+
if (warnings.length) result.warning = [...new Set([result.warning, ...warnings].filter(Boolean))].join(" ") || undefined;
|
|
111
|
+
return result;
|
|
79
112
|
}
|
|
80
113
|
|
|
81
114
|
async function loadBrowserPage(
|
|
@@ -106,34 +139,36 @@ async function loadBrowserPage(
|
|
|
106
139
|
catch (error) { if (signal?.aborted) throw error; options.onWarning?.(`${previous}: performance ranking unavailable.`); return new Map<string, number | null>(); }
|
|
107
140
|
}));
|
|
108
141
|
const funds: ThirteenFFund[] = topFunds.map((fund) => ({ cik: fund.cik, name: fund.name }));
|
|
109
|
-
const {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
});
|
|
113
|
-
return {
|
|
114
|
-
rows: buildBrowserRows({ topFunds, reports, source: "performance" }).map(row => ({ ...row, priorReturns: quarters.map((previous, index) => ({ quarter: previous, value: history[index]?.get(row.cik) ?? null })) })),
|
|
115
|
-
warning,
|
|
142
|
+
const rows = buildBrowserRows({ topFunds, source: "performance" }).map(row => ({ ...row, priorReturns: quarters.map((previous, index) => ({ quarter: previous, value: history[index]?.get(row.cik) ?? null })) }));
|
|
143
|
+
const result: BrowserLoadResult = {
|
|
144
|
+
rows,
|
|
116
145
|
quarter,
|
|
117
146
|
period: topFunds[0]?.periodOfReport,
|
|
118
147
|
hasMore: topFunds.length >= browserLimit,
|
|
119
148
|
nextOffset: offset + topFunds.length,
|
|
120
149
|
};
|
|
150
|
+
fillReports(result, rows, loadReportsForFunds(funds.slice(0, Math.min(browserLimit, FORM_ENRICHMENT_LIMIT)), {
|
|
151
|
+
from, to, signal, ...apiOptions,
|
|
152
|
+
periods: new Map(topFunds.map((fund) => [fund.cik, fund.periodOfReport])),
|
|
153
|
+
}), (reports) => buildBrowserRows({ topFunds, reports, source: "performance" }), signal);
|
|
154
|
+
return result;
|
|
121
155
|
}
|
|
122
156
|
|
|
123
157
|
if (tab === "latest") {
|
|
124
158
|
const filings = await listThirteenFFilings(isoDateDaysAgo(21, now), to, latestLimit, signal, pageApiOptions);
|
|
125
159
|
const selected = dedupeLatestForms(filings);
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
});
|
|
130
|
-
return {
|
|
131
|
-
rows: buildBrowserRows({ latestFilings: filings, reports, source: "latest" }),
|
|
132
|
-
warning,
|
|
160
|
+
const rows = buildBrowserRows({ latestFilings: filings, source: "latest" });
|
|
161
|
+
const result: BrowserLoadResult = {
|
|
162
|
+
rows,
|
|
133
163
|
period: filings[0]?.periodOfReport,
|
|
134
164
|
hasMore: filings.length >= latestLimit,
|
|
135
165
|
nextOffset: offset + filings.length,
|
|
136
166
|
};
|
|
167
|
+
fillReports(result, rows, loadReportsForFunds(selected.map((form) => ({ cik: form.cik, name: form.companyName })), {
|
|
168
|
+
from, to, signal, ...apiOptions,
|
|
169
|
+
periods: new Map(selected.map((form) => [form.cik, form.periodOfReport])),
|
|
170
|
+
}), (reports) => buildBrowserRows({ latestFilings: filings, reports, source: "latest" }), signal);
|
|
171
|
+
return result;
|
|
137
172
|
}
|
|
138
173
|
|
|
139
174
|
if (tab === "byTicker") {
|
|
@@ -155,18 +190,23 @@ async function loadBrowserPage(
|
|
|
155
190
|
try {
|
|
156
191
|
const holders = await lookupThirteenFHoldersByCusip(cusip, periodOfReport, signal, apiOptions);
|
|
157
192
|
const pageCiks = holders.ciks.slice(offset, offset + browserLimit);
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const funds = pageCiks.map((cik) => ({ cik, name: reports.get(cik)?.filings.at(-1)?.companyName || cik }));
|
|
163
|
-
return {
|
|
164
|
-
rows: buildBrowserRows({ funds, reports, source: "ticker" }),
|
|
165
|
-
warning,
|
|
193
|
+
const funds = pageCiks.map((cik) => ({ cik, name: cik }));
|
|
194
|
+
const rows = buildBrowserRows({ funds, source: "ticker" });
|
|
195
|
+
const result: BrowserLoadResult = {
|
|
196
|
+
rows,
|
|
166
197
|
period: holders.periodOfReport,
|
|
167
198
|
hasMore: holders.ciks.length > offset + pageCiks.length,
|
|
168
199
|
nextOffset: offset + pageCiks.length,
|
|
169
200
|
};
|
|
201
|
+
fillReports(result, rows, loadReportsForFunds(funds, {
|
|
202
|
+
from, to, signal, ...apiOptions,
|
|
203
|
+
periods: new Map(pageCiks.map((cik) => [cik, holders.periodOfReport])),
|
|
204
|
+
}), (reports) => buildBrowserRows({
|
|
205
|
+
funds: pageCiks.map((cik) => ({ cik, name: reports.get(cik)?.filings.at(-1)?.companyName || cik })),
|
|
206
|
+
reports,
|
|
207
|
+
source: "ticker",
|
|
208
|
+
}), signal);
|
|
209
|
+
return result;
|
|
170
210
|
} catch (error) {
|
|
171
211
|
return {
|
|
172
212
|
rows: [],
|
|
@@ -195,26 +235,27 @@ async function loadBrowserPage(
|
|
|
195
235
|
}
|
|
196
236
|
const found = await searchThirteenFFunds(trimmed, browserLimit, signal, pageApiOptions);
|
|
197
237
|
const funds = oneFundPerCik(found);
|
|
198
|
-
const
|
|
199
|
-
loadReportsForFunds(funds.slice(0, Math.min(browserLimit, FORM_ENRICHMENT_LIMIT)), { from, to, signal, ...apiOptions }),
|
|
200
|
-
listTopThirteenFFunds(quarter, browserLimit, signal, apiOptions).catch(() => []),
|
|
201
|
-
]);
|
|
238
|
+
const topFunds = await listTopThirteenFFunds(quarter, browserLimit, signal, apiOptions).catch((): ThirteenFTopFund[] => []);
|
|
202
239
|
const topByCik = new Map(topFunds.map((fund) => [fund.cik, fund]));
|
|
203
240
|
const matchedTopFunds = funds
|
|
204
241
|
.map((fund) => topByCik.get(fund.cik))
|
|
205
242
|
.filter((fund): fund is ThirteenFTopFund => !!fund);
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
topFunds: matchedTopFunds,
|
|
210
|
-
reports,
|
|
211
|
-
source: "funds",
|
|
212
|
-
}),
|
|
243
|
+
const rows = buildBrowserRows({ funds: oneFundPerCik(found), topFunds: matchedTopFunds, source: "funds" });
|
|
244
|
+
const result: BrowserLoadResult = {
|
|
245
|
+
rows,
|
|
213
246
|
quarter,
|
|
214
|
-
warning,
|
|
215
247
|
hasMore: found.length >= browserLimit,
|
|
216
248
|
nextOffset: offset + found.length,
|
|
217
249
|
};
|
|
250
|
+
fillReports(result, rows, loadReportsForFunds(funds.slice(0, Math.min(browserLimit, FORM_ENRICHMENT_LIMIT)), {
|
|
251
|
+
from, to, signal, ...apiOptions,
|
|
252
|
+
}), (reports) => buildBrowserRows({
|
|
253
|
+
funds: oneFundPerCik(found, reports),
|
|
254
|
+
topFunds: matchedTopFunds,
|
|
255
|
+
reports,
|
|
256
|
+
source: "funds",
|
|
257
|
+
}), signal);
|
|
258
|
+
return result;
|
|
218
259
|
}
|
|
219
260
|
|
|
220
261
|
export async function loadFundDetail(
|
|
@@ -257,6 +257,7 @@ async function findThirteenFFund(
|
|
|
257
257
|
if (isCikQuery(query)) return { cik: normalizeCik(query), name: query };
|
|
258
258
|
// A small output limit must not hide other matching managers during lookup.
|
|
259
259
|
const result = await search(query, 25);
|
|
260
|
+
await result.reportsLoaded;
|
|
260
261
|
const candidates = [...new Map(result.rows.map((row) => [row.cik, row])).values()];
|
|
261
262
|
const exact = candidates.filter((row) => row.name.toLocaleLowerCase() === query.toLocaleLowerCase());
|
|
262
263
|
if (exactOnly) return exact.length === 1 ? { cik: exact[0]!.cik, name: exact[0]!.name } : null;
|
|
@@ -603,6 +604,7 @@ export function createThirteenFHeadless(
|
|
|
603
604
|
|
|
604
605
|
const tab = browserTab(view === "ticker-holdings" ? requestedView : view, query);
|
|
605
606
|
const result = await dependencies.loadBrowser(tab, query, limit, args, ctx);
|
|
607
|
+
await result.reportsLoaded;
|
|
606
608
|
const more = result.hasMore || result.rows.length > limit;
|
|
607
609
|
const browserRows = sortBrowserRows(result.rows, browserSortFor(DEFAULT_BROWSER_SORT, tab)).slice(0, limit);
|
|
608
610
|
// A search finds managers that stopped filing; say which, since the Period column alone reads like any other.
|
|
@@ -113,13 +113,21 @@ function ThirteenFBrowserPane({ focused, width, height, onDetailChange }: PanePr
|
|
|
113
113
|
// A new query starts from an empty list; a refresh keeps the rows it has.
|
|
114
114
|
const browser = usePagedRows(showTickerHoldings ? null : loadPage, { getId: rowId });
|
|
115
115
|
const { loading, loadingMore, rows } = browser;
|
|
116
|
+
const [reportPaint, setReportPaint] = useState(0);
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
let alive = true;
|
|
119
|
+
void Promise.all(browser.pages.map((page) => page.reportsLoaded ?? Promise.resolve())).then(() => {
|
|
120
|
+
if (alive) setReportPaint((value) => value + 1);
|
|
121
|
+
}, () => {});
|
|
122
|
+
return () => { alive = false; };
|
|
123
|
+
}, [browser.pages]);
|
|
116
124
|
const error = browser.error?.message ?? null;
|
|
117
125
|
// A refresh clears the pages' warnings until it answers.
|
|
118
126
|
const pagesSettled = !loading && !browser.error;
|
|
119
127
|
const warning = useMemo(() => {
|
|
120
128
|
const notes = [...(pagesSettled ? browser.pages.map((page) => page.warning) : []), browser.moreError?.message];
|
|
121
129
|
return [...new Set(notes.filter(Boolean))].join(" ") || null;
|
|
122
|
-
}, [browser.moreError, browser.pages, pagesSettled]);
|
|
130
|
+
}, [browser.moreError, browser.pages, pagesSettled, reportPaint]);
|
|
123
131
|
const [detailSeed, setDetailSeed] = useState<FundSeed | null>(() => (
|
|
124
132
|
initialCik ? { cik: String(initialCik), name: normalizedQuery || String(initialCik) } : null
|
|
125
133
|
));
|
|
@@ -157,7 +165,7 @@ function ThirteenFBrowserPane({ focused, width, height, onDetailChange }: PanePr
|
|
|
157
165
|
}, { allowEditable: true });
|
|
158
166
|
|
|
159
167
|
const browserSort = useMemo(() => browserSortFor(sortPreference, browserMode), [sortPreference, browserMode]);
|
|
160
|
-
const sortedRows = useMemo(() => sortBrowserRows(rows, browserSort), [rows, browserSort]);
|
|
168
|
+
const sortedRows = useMemo(() => sortBrowserRows(rows, browserSort), [rows, browserSort, reportPaint]);
|
|
161
169
|
const columns = useMemo(() => {
|
|
162
170
|
if (browserMode !== "performance") return buildBrowserColumns(false);
|
|
163
171
|
const history = rows[0]?.priorReturns ?? [];
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type DataTableCell,
|
|
11
11
|
type DataTableColumn,
|
|
12
12
|
type DataTableKeyEvent,
|
|
13
|
+
type PaneFooterSegment,
|
|
13
14
|
} from "../../../../components";
|
|
14
15
|
import { TIME_RANGES, type TimeRange } from "../../../../time-series/range";
|
|
15
16
|
import type { PaneProps } from "../../../../types/plugin";
|
|
@@ -29,6 +30,18 @@ import {
|
|
|
29
30
|
import { usePaneTicker } from "../../../../state/app/context";
|
|
30
31
|
import { handleRefreshKey, useClampSelectedIndex } from "../../../../components/data-table/table-pane";
|
|
31
32
|
import { useBoundTicker, useTickerRequest } from "../../shared/ticker-request";
|
|
33
|
+
import { DAY_SO_FAR_TEXT, withSessionBar } from "../../../../time-series/session-bar";
|
|
34
|
+
import {
|
|
35
|
+
formingHistoryBar,
|
|
36
|
+
formingHistoryBarText,
|
|
37
|
+
HISTORY_DEFAULT_RANGE,
|
|
38
|
+
historyBarDate,
|
|
39
|
+
historyBarSize,
|
|
40
|
+
historyBarSizeText,
|
|
41
|
+
historyBarSizeWord,
|
|
42
|
+
historyDateHeader,
|
|
43
|
+
type HistoryBarSize,
|
|
44
|
+
} from "./history-bars";
|
|
32
45
|
|
|
33
46
|
type HistoryColumnId = "date" | "open" | "high" | "low" | "close" | "change" | "changePercent" | "volume";
|
|
34
47
|
type HistoryColumn = DataTableColumn & { id: HistoryColumnId };
|
|
@@ -36,7 +49,14 @@ type HistoryColumn = DataTableColumn & { id: HistoryColumnId };
|
|
|
36
49
|
export type HistoricalPriceRow = {
|
|
37
50
|
key: string;
|
|
38
51
|
point: ReturnType<typeof pricePointValues>;
|
|
52
|
+
/** The bar's date as the table reads it: `2026-10 MTD` for a month still running, `2026-10-16 DTD` for today's session. */
|
|
39
53
|
date: string;
|
|
54
|
+
/** What a CSV export writes for the date: the bar's own date, whatever the table says of its period. */
|
|
55
|
+
exportDate: string;
|
|
56
|
+
/** The bar carries a time of day, so the table dates it with one. */
|
|
57
|
+
timed: boolean;
|
|
58
|
+
/** The bar's period had not ended: a week or month still running, or the day's session still trading. */
|
|
59
|
+
partial: boolean;
|
|
40
60
|
change: number | null;
|
|
41
61
|
changePercent: number | null;
|
|
42
62
|
};
|
|
@@ -90,25 +110,49 @@ function formatMaybeCompact(value: number | null | undefined): string {
|
|
|
90
110
|
return value == null ? "-" : formatCompact(value);
|
|
91
111
|
}
|
|
92
112
|
|
|
93
|
-
|
|
113
|
+
interface HistoricalPriceTable {
|
|
114
|
+
/** Newest first. */
|
|
115
|
+
rows: HistoricalPriceRow[];
|
|
116
|
+
size: HistoryBarSize;
|
|
117
|
+
/** The newest bar's week or month had not ended, and how far into it the bar runs. */
|
|
118
|
+
forming: ReturnType<typeof formingHistoryBar>;
|
|
119
|
+
/** The newest bar is a day whose session is still trading. */
|
|
120
|
+
dayToDate: boolean;
|
|
121
|
+
/** The newest bar's day, `YYYY-MM-DD`. */
|
|
122
|
+
newestDate: string | undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function buildHistoricalPriceRows(points: PricePoint[], venue: string | undefined, now: number): HistoricalPriceTable {
|
|
94
126
|
const sorted = points
|
|
95
127
|
.flatMap((point, sourceIndex) => {
|
|
96
128
|
const date = pricePointDate(point);
|
|
97
|
-
return date ? [{ point: pricePointValues(point), date, sourceIndex }] : [];
|
|
129
|
+
return date ? [{ point: pricePointValues(point), date, sourceIndex, partial: point.partial === true }] : [];
|
|
98
130
|
})
|
|
99
131
|
.sort((left, right) => left.date.getTime() - right.date.getTime());
|
|
100
|
-
|
|
132
|
+
const size = historyBarSize(sorted.map((entry) => entry.date.getTime()));
|
|
133
|
+
const newestDate = sorted.at(-1)?.date.toISOString().slice(0, 10);
|
|
134
|
+
const forming = formingHistoryBar(size, newestDate, venue, now);
|
|
135
|
+
const dayToDate = size === "1d" && sorted.at(-1)?.partial === true;
|
|
136
|
+
const rows = sorted.map((entry, index) => {
|
|
101
137
|
const previous = sorted[index - 1]?.point;
|
|
102
138
|
const { point, date, sourceIndex } = entry;
|
|
139
|
+
const newest = index === sorted.length - 1;
|
|
140
|
+
const partial = newest && (!!forming || dayToDate);
|
|
103
141
|
const change = previous?.close != null && point.close != null ? point.close - previous.close : null;
|
|
142
|
+
const shown = formatDateTime(date);
|
|
143
|
+
const timed = shown.length > 10;
|
|
104
144
|
return {
|
|
105
145
|
key: `${date.toISOString()}:${sourceIndex}`,
|
|
106
146
|
point,
|
|
107
|
-
date:
|
|
147
|
+
date: timed ? shown : historyBarDate(shown, size, partial),
|
|
148
|
+
exportDate: shown,
|
|
149
|
+
timed,
|
|
150
|
+
partial,
|
|
108
151
|
change,
|
|
109
152
|
changePercent: previous?.close && change !== null ? change / previous.close : null,
|
|
110
153
|
};
|
|
111
154
|
}).reverse();
|
|
155
|
+
return { rows, size, forming, dayToDate, newestDate };
|
|
112
156
|
}
|
|
113
157
|
|
|
114
158
|
const HISTORY_PRICE_WIDTH = 10;
|
|
@@ -123,8 +167,9 @@ const HISTORY_NUMERIC_IDS = ["open", "high", "low", "close", "change", "changePe
|
|
|
123
167
|
* evenly, and give up cells down to a floor when it is narrow, so the volume
|
|
124
168
|
* stays on screen.
|
|
125
169
|
*/
|
|
126
|
-
function buildHistoryColumns(width: number, intraday: boolean): HistoryColumn[] {
|
|
127
|
-
|
|
170
|
+
function buildHistoryColumns(width: number, intraday: boolean, size: HistoryBarSize, newestDate: string): HistoryColumn[] {
|
|
171
|
+
// A month reads as `2026-10`; the newest bar, when its week or month runs on, adds `MTD` or `WTD`.
|
|
172
|
+
const dateWidth = Math.max(intraday ? 16 : size === "1mo" ? 7 : 10, newestDate.length);
|
|
128
173
|
const numeric: number[] = HISTORY_NUMERIC_IDS.map((id) => id === "changePercent" ? HISTORY_PERCENT_WIDTH : HISTORY_PRICE_WIDTH);
|
|
129
174
|
// One gap per column plus the table's inset on both sides.
|
|
130
175
|
const available = width - dateWidth - HISTORY_VOLUME_WIDTH - 8 - 2;
|
|
@@ -141,7 +186,7 @@ function buildHistoryColumns(width: number, intraday: boolean): HistoryColumn[]
|
|
|
141
186
|
}
|
|
142
187
|
const [open, high, low, close, change, changePercent] = numeric as [number, number, number, number, number, number];
|
|
143
188
|
return [
|
|
144
|
-
{ id: "date", label: intraday ? "DATE/TIME" : "DATE", width: dateWidth, align: "left" },
|
|
189
|
+
{ id: "date", label: intraday ? "DATE/TIME" : historyDateHeader(size)?.toUpperCase() ?? "DATE", width: dateWidth, align: "left" },
|
|
145
190
|
{ id: "open", label: "OPEN", width: open, align: "right", flexGrow: 1 },
|
|
146
191
|
{ id: "high", label: "HIGH", width: high, align: "right", flexGrow: 1 },
|
|
147
192
|
{ id: "low", label: "LOW", width: low, align: "right", flexGrow: 1 },
|
|
@@ -162,7 +207,7 @@ function nextHistoryRange(current: TimeRange): TimeRange {
|
|
|
162
207
|
export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
|
|
163
208
|
const dataProvider = useAssetData();
|
|
164
209
|
const { symbol, exchange, ticker } = useBoundTicker();
|
|
165
|
-
const [range, setRange] = usePluginPaneState<TimeRange>("range",
|
|
210
|
+
const [range, setRange] = usePluginPaneState<TimeRange>("range", HISTORY_DEFAULT_RANGE);
|
|
166
211
|
const [selectedIdx, setSelectedIdx] = useDebouncedPluginPaneState<number>("selectedIdx", 0);
|
|
167
212
|
const loader = useCallback((nextSymbol: string, nextExchange: string, forceRefresh: boolean) => {
|
|
168
213
|
if (!dataProvider) throw new Error("Market data unavailable");
|
|
@@ -174,14 +219,27 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
|
|
|
174
219
|
);
|
|
175
220
|
}, [dataProvider, range]);
|
|
176
221
|
const { data, loading, error, reload } = useTickerRequest<PricePoint[]>(loader, symbol, exchange);
|
|
177
|
-
const rows = useMemo(() => buildHistoricalPriceRows(data ?? []), [data]);
|
|
178
|
-
const integrityNotice = priceHistoryIntegrityNotice(rows.filter((row) => row.point.integrity).length);
|
|
179
|
-
// Daily and longer bars are dates; only intraday bars carry a time.
|
|
180
|
-
const intraday = rows.length > 0 ? rows.some((row) => row.date.length > 10) : range === "1D" || range === "1W";
|
|
181
|
-
const columns = useMemo(() => buildHistoryColumns(width, intraday), [intraday, width]);
|
|
182
222
|
// The quote knows the instrument type even when the saved ticker does not;
|
|
183
|
-
// resolve it the way every quote display does.
|
|
223
|
+
// resolve it the way every quote display does. It also names the venue
|
|
224
|
+
// whose sessions say whether a week or month is still running.
|
|
184
225
|
const { financials } = usePaneTicker();
|
|
226
|
+
const venue = exchange || financials?.quote?.listingExchangeName || financials?.quote?.exchangeName || undefined;
|
|
227
|
+
// While the session trades the day's bar is the quote's: the history keeps a
|
|
228
|
+
// snapshot of it from whenever it was last fetched (withSessionBar).
|
|
229
|
+
const quote = financials?.quote;
|
|
230
|
+
const points = useMemo(
|
|
231
|
+
() => withSessionBar(data ?? [], quote, { now: Date.now(), venue }),
|
|
232
|
+
[data, quote, venue],
|
|
233
|
+
);
|
|
234
|
+
const { rows, size, forming, dayToDate, newestDate } = useMemo(
|
|
235
|
+
() => buildHistoricalPriceRows(points, venue, Date.now()),
|
|
236
|
+
[points, venue],
|
|
237
|
+
);
|
|
238
|
+
const integrityNotice = priceHistoryIntegrityNotice(rows.filter((row) => row.point.integrity).length);
|
|
239
|
+
// Daily and longer bars are dates; only intraday bars carry a time.
|
|
240
|
+
const intraday = rows.length > 0 ? rows.some((row) => row.timed) : range === "1D" || range === "1W";
|
|
241
|
+
const newestRowDate = rows[0]?.date ?? "";
|
|
242
|
+
const columns = useMemo(() => buildHistoryColumns(width, intraday, size, newestRowDate), [intraday, newestRowDate, size, width]);
|
|
185
243
|
const assetCategory = quoteFormatOptions(
|
|
186
244
|
financials?.quote,
|
|
187
245
|
ticker?.metadata.assetCategory,
|
|
@@ -217,7 +275,7 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
|
|
|
217
275
|
): DataTableCell => {
|
|
218
276
|
switch (column.id) {
|
|
219
277
|
case "date":
|
|
220
|
-
return { text: row.date, color: colors.textDim };
|
|
278
|
+
return { text: row.date, value: row.exportDate, color: colors.textDim };
|
|
221
279
|
case "open":
|
|
222
280
|
case "high":
|
|
223
281
|
case "low":
|
|
@@ -236,8 +294,15 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
|
|
|
236
294
|
}
|
|
237
295
|
}, [priceDecimals]);
|
|
238
296
|
|
|
239
|
-
// The range is picked in the query bar; t still steps through it.
|
|
240
|
-
|
|
297
|
+
// The range is picked in the query bar; t still steps through it. The range
|
|
298
|
+
// sets the bar size, which the footer says, and a week or month still
|
|
299
|
+
// running says how far the newest bar goes.
|
|
300
|
+
const barInfo = useMemo<PaneFooterSegment[]>(() => [
|
|
301
|
+
...rows.length > 0 ? [{ id: "bars", parts: [{ text: historyBarSizeText(size), tone: "muted" as const }] }] : [],
|
|
302
|
+
...forming && newestDate ? [{ id: "forming", parts: [{ text: formingHistoryBarText(forming, newestDate), tone: "muted" as const }] }] : [],
|
|
303
|
+
...dayToDate ? [{ id: "forming", parts: [{ text: DAY_SO_FAR_TEXT, tone: "muted" as const }] }] : [],
|
|
304
|
+
], [dayToDate, forming, newestDate, rows.length, size]);
|
|
305
|
+
usePaneStatusFooter({ registrationId: "historical-prices", loading, error, info: barInfo });
|
|
241
306
|
|
|
242
307
|
usePaneNoticeFooter({
|
|
243
308
|
registrationId: "historical-prices:integrity",
|
|
@@ -280,7 +345,10 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
|
|
|
280
345
|
getExportMetadata={() => [
|
|
281
346
|
["Ticker", symbol ? publicTickerKey(symbol, exchange) : ""],
|
|
282
347
|
["Requested range", range],
|
|
348
|
+
...(rows.length > 0 ? [["Bar size", historyBarSizeWord(size)]] : []),
|
|
283
349
|
["Time zone", "UTC"],
|
|
350
|
+
...(forming && newestDate ? [["Partial bar", `${newestDate}, ${formingHistoryBarText(forming, newestDate)}`]] : []),
|
|
351
|
+
...(dayToDate && newestDate ? [["Partial bar", `${newestDate}, ${DAY_SO_FAR_TEXT}`]] : []),
|
|
284
352
|
["Price units", "As served in the history; currency and price-basis metadata are unavailable"],
|
|
285
353
|
["Status", error ? (rows.length ? "Retained after refresh failure" : "Unavailable")
|
|
286
354
|
: loading ? (rows.length ? "Refreshing" : "Loading") : rows.length ? "Available" : "Empty"],
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { BAR_INTERVAL_LABELS } from "../../../../cli/history-rows";
|
|
2
|
+
import { periodToDateText } from "../../../../cli/pane-functions/freshness";
|
|
3
|
+
import { barPeriodToDate, type BarPeriod, type BarPeriodToDate } from "../../../../market-data/market/bar-period";
|
|
4
|
+
import { barSizeFromGap, medianBarGap } from "../../../../time-series/bar-spacing";
|
|
5
|
+
import type { TimeRange } from "../../../../time-series/range";
|
|
6
|
+
import { CHART_RESOLUTION_STEP_MS, type ManualChartResolution } from "../../../../time-series/resolution";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The range a price history opens on, in Historical Prices, `fn HP` and
|
|
10
|
+
* `shot HP`: a recent window in daily bars (RANGE_HISTORY_RESOLUTION serves
|
|
11
|
+
* 1M to 1Y daily). 5Y is weekly and ALL monthly, so neither is a place to start.
|
|
12
|
+
*/
|
|
13
|
+
export const HISTORY_DEFAULT_RANGE: TimeRange = "3M";
|
|
14
|
+
|
|
15
|
+
/** The size of one bar of a price history; `intraday` when its minutes are not a size the source serves. */
|
|
16
|
+
export type HistoryBarSize = ManualChartResolution | "intraday";
|
|
17
|
+
|
|
18
|
+
const INTRADAY_SIZES: readonly ManualChartResolution[] = ["1m", "5m", "15m", "30m", "45m", "1h"];
|
|
19
|
+
/** A day's bars can sit a little under a day apart where the clocks change. */
|
|
20
|
+
const INTRADAY_BELOW_MS = 0.8 * CHART_RESOLUTION_STEP_MS["1d"];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The bar size of a history, read from the usual spacing of its bars
|
|
24
|
+
* (medianBarGap), as `history` reads it, so the gap a holiday leaves does not
|
|
25
|
+
* make daily bars weekly. Daily when there is no gap to read.
|
|
26
|
+
*/
|
|
27
|
+
export function historyBarSize(times: readonly number[]): HistoryBarSize {
|
|
28
|
+
const gap = medianBarGap(times);
|
|
29
|
+
if (gap == null) return "1d";
|
|
30
|
+
if (gap < INTRADAY_BELOW_MS) return INTRADAY_SIZES.find((size) => CHART_RESOLUTION_STEP_MS[size] === gap) ?? "intraday";
|
|
31
|
+
return barSizeFromGap(gap);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Whether a bar of this size covers a day or more, so its date is a day and not a time. */
|
|
35
|
+
export function isDailyOrLongerBar(size: HistoryBarSize): size is "1d" | "1wk" | "1mo" {
|
|
36
|
+
return size === "1d" || size === "1wk" || size === "1mo";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** `daily`, `5-minute`, `hourly`, `weekly`, `monthly`; `intraday` when the minutes are not a known size. */
|
|
40
|
+
export function historyBarSizeWord(size: HistoryBarSize): string {
|
|
41
|
+
return size === "intraday" ? size : BAR_INTERVAL_LABELS[size];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** `daily bars`, `monthly bars`: what a history says its bars are, which the range it was asked for sets. */
|
|
45
|
+
export function historyBarSizeText(size: HistoryBarSize): string {
|
|
46
|
+
return `${historyBarSizeWord(size)} bars`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* What a date column heads when its bars cover more than a day: `Week of`
|
|
51
|
+
* (each is dated by the day its week starts) or `Month`. Null for a day's bar,
|
|
52
|
+
* whose column is `Date`, or an intraday one, which is dated with a time.
|
|
53
|
+
*/
|
|
54
|
+
export function historyDateHeader(size: HistoryBarSize): string | null {
|
|
55
|
+
return size === "1wk" ? "Week of" : size === "1mo" ? "Month" : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The calendar period a weekly or monthly bar covers; null for any other. */
|
|
59
|
+
function historyBarPeriod(size: HistoryBarSize): BarPeriod | null {
|
|
60
|
+
return size === "1mo" ? "month" : size === "1wk" ? "week" : null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Whether the newest bar, dated `date`, is a week or month that had not ended
|
|
65
|
+
* at `now` on its `venue` (barPeriodToDate), and how far into it the bar runs.
|
|
66
|
+
* Null for a complete bar, and for any bar that is not weekly or monthly.
|
|
67
|
+
*/
|
|
68
|
+
export function formingHistoryBar(
|
|
69
|
+
size: HistoryBarSize,
|
|
70
|
+
date: string | undefined,
|
|
71
|
+
venue: string | undefined,
|
|
72
|
+
now: number,
|
|
73
|
+
): BarPeriodToDate | null {
|
|
74
|
+
const period = historyBarPeriod(size);
|
|
75
|
+
return period && date ? barPeriodToDate(date, period, venue, now) : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* A bar's day as its table dates it: a month's bar as the month (`2026-10`),
|
|
80
|
+
* a week's by the day it starts, and `MTD`, `WTD` or `DTD` after one whose
|
|
81
|
+
* period had not ended, so it never reads as a complete bar dated on its
|
|
82
|
+
* first day. `day` is `YYYY-MM-DD`.
|
|
83
|
+
*/
|
|
84
|
+
export function historyBarDate(day: string, size: HistoryBarSize, partial = false): string {
|
|
85
|
+
const text = size === "1mo" ? day.slice(0, 7) : day;
|
|
86
|
+
return partial ? `${text} ${size === "1mo" ? "MTD" : size === "1wk" ? "WTD" : "DTD"}` : text;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** What the newest bar covers so far: `month to date, through Fri 16 Oct 2026`. */
|
|
90
|
+
export function formingHistoryBarText(forming: BarPeriodToDate, date: string): string {
|
|
91
|
+
return periodToDateText({ period: forming.period, ...(forming.through ? { through: forming.through } : {}) }, date);
|
|
92
|
+
}
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
type ReturnRow,
|
|
43
43
|
} from "./returns-model";
|
|
44
44
|
import { loadReturnSeries, type ReturnSeries } from "./returns-history";
|
|
45
|
+
import { formingHistoryBar, formingHistoryBarText, historyBarDate } from "./history-bars";
|
|
45
46
|
|
|
46
47
|
type ReturnColumnId = "time" | "price" | "intervalChange" | "intervalPercent" | "cumulativeChange" | "cumulativePercent";
|
|
47
48
|
type ReturnColumn = DataTableColumn & { id: ReturnColumnId };
|
|
@@ -185,12 +186,21 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
|
|
|
185
186
|
const hints = useMemo<PaneHint[]>(() => (
|
|
186
187
|
table.searchable ? [{ id: "search", key: "/", label: "search", onPress: focusSearch }] : []
|
|
187
188
|
), [focusSearch, table.searchable]);
|
|
189
|
+
// A weekly or monthly interval's newest bar is its week or month so far when that has not ended.
|
|
190
|
+
const newestDate = returnRows[0] ? new Date(returnRows[0].timestamp).toISOString().slice(0, 10) : undefined;
|
|
191
|
+
const forming = useMemo(
|
|
192
|
+
() => data ? formingHistoryBar(data.resolution, newestDate, data.venue || undefined, Date.now()) : null,
|
|
193
|
+
[data, newestDate],
|
|
194
|
+
);
|
|
195
|
+
const formingKey = forming ? returnRows[0]?.key : undefined;
|
|
196
|
+
const barSize = data?.resolution ?? shownResolution;
|
|
188
197
|
// A shut market's window ends at its last session, which the footer names.
|
|
189
|
-
const marketInfo = useMemo<PaneFooterSegment[]>(() =>
|
|
190
|
-
data?.lastSession
|
|
191
|
-
? [{ id: "market", parts: [{ text: closedMarketNote(data.lastSession, data.visibleTo), tone: "muted" }] }]
|
|
192
|
-
: []
|
|
193
|
-
|
|
198
|
+
const marketInfo = useMemo<PaneFooterSegment[]>(() => [
|
|
199
|
+
...data?.lastSession
|
|
200
|
+
? [{ id: "market", parts: [{ text: closedMarketNote(data.lastSession, data.visibleTo), tone: "muted" as const }] }]
|
|
201
|
+
: [],
|
|
202
|
+
...forming && newestDate ? [{ id: "forming", parts: [{ text: formingHistoryBarText(forming, newestDate), tone: "muted" as const }] }] : [],
|
|
203
|
+
], [data, forming, newestDate]);
|
|
194
204
|
usePaneStatusFooter({ registrationId: "returns", loading, error, hints, info: marketInfo });
|
|
195
205
|
|
|
196
206
|
const integrityNotice = priceHistoryIntegrityNotice(returnRows.filter((row) => row.inconsistent).length);
|
|
@@ -208,8 +218,15 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
|
|
|
208
218
|
|
|
209
219
|
const renderCell = useCallback((row: ReturnRow, column: ReturnColumn): DataTableCell => {
|
|
210
220
|
switch (column.id) {
|
|
211
|
-
case "time":
|
|
212
|
-
|
|
221
|
+
case "time": {
|
|
222
|
+
const shown = formatDateTime(new Date(row.timestamp));
|
|
223
|
+
// A daily or longer bar is a date; a month's reads as the month, and one still running says so.
|
|
224
|
+
return {
|
|
225
|
+
text: shown.length > 10 ? shown : historyBarDate(shown, barSize, row.key === formingKey),
|
|
226
|
+
value: new Date(row.timestamp).toISOString(),
|
|
227
|
+
color: colors.textDim,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
213
230
|
case "price":
|
|
214
231
|
return { text: formatMaybePrice(row.price, priceDecimals, column.width), value: exportPrice(row.price, priceDecimals), color: colors.textBright, attributes: TextAttributes.BOLD };
|
|
215
232
|
case "intervalChange":
|
|
@@ -221,7 +238,7 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
|
|
|
221
238
|
case "cumulativePercent":
|
|
222
239
|
return { text: formatMaybePercent(row.cumulativePercent), value: row.cumulativePercent === null ? null : row.cumulativePercent * 100, color: priceColor(row.cumulativePercent ?? 0) };
|
|
223
240
|
}
|
|
224
|
-
}, [priceDecimals]);
|
|
241
|
+
}, [barSize, formingKey, priceDecimals]);
|
|
225
242
|
|
|
226
243
|
return (
|
|
227
244
|
<DataTableView<ReturnRow, ReturnColumn>
|
|
@@ -270,6 +287,7 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
|
|
|
270
287
|
["Ticker", symbol ? publicTickerKey(symbol, exchange) : ""],
|
|
271
288
|
["Range", data?.range ?? range],
|
|
272
289
|
["Interval", getChartResolutionLabel(data?.resolution ?? shownResolution)],
|
|
290
|
+
...(forming && newestDate ? [["Partial bar", `${newestDate}, ${formingHistoryBarText(forming, newestDate)}`]] : []),
|
|
273
291
|
["Window", data ? `after ${new Date(data.visibleFrom).toISOString()} through ${new Date(data.visibleTo).toISOString()}` : ""],
|
|
274
292
|
...(data?.lastSession ? [["Market", closedMarketNote(data.lastSession, data.visibleTo)]] : []),
|
|
275
293
|
["Time zone", "UTC, bar open"],
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { HeadlessPaneDefinition } from "../../../types/headless";
|
|
2
2
|
import { FINANCIAL_PERIOD_OPTION, HISTORY_RANGE_VALUES } from "../shared/headless-options";
|
|
3
|
+
import { historyIntervalsByRange } from "../../../cli/history-rows";
|
|
4
|
+
import { HISTORY_DEFAULT_RANGE } from "./data-panes/history-bars";
|
|
3
5
|
import { RETURN_INTERVAL_VALUES, RETURN_RANGES } from "./data-panes/returns-model";
|
|
4
6
|
|
|
5
7
|
type PaneSchema = Pick<HeadlessPaneDefinition, "argument" | "options" | "discovery">;
|
|
@@ -51,11 +53,11 @@ export const paneSchemas = {
|
|
|
51
53
|
options: [
|
|
52
54
|
{
|
|
53
55
|
key: "range",
|
|
54
|
-
description:
|
|
56
|
+
description: `Price-history window, which also sets the bar size: ${historyIntervalsByRange()}.`,
|
|
55
57
|
type: "enum",
|
|
56
58
|
aliases: ["rangePreset"],
|
|
57
59
|
values: HISTORY_RANGE_VALUES,
|
|
58
|
-
defaultValue:
|
|
60
|
+
defaultValue: HISTORY_DEFAULT_RANGE,
|
|
59
61
|
pluginState: { pluginId: "ticker-research" },
|
|
60
62
|
},
|
|
61
63
|
],
|