gloomberb 0.16.6 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/api-client/chat-pane-link.ts +47 -0
- package/src/api-client/chat.ts +9 -1
- package/src/api-client/data.ts +37 -1
- package/src/api-client/equity-screener.ts +3 -0
- package/src/api-client/europe-energy.ts +91 -0
- package/src/api-client/index.ts +6 -0
- package/src/api-client/letters.ts +41 -0
- package/src/api-client/normalizers.ts +36 -6
- package/src/api-client/organization-tickers.ts +44 -0
- package/src/api-client/request.ts +3 -1
- package/src/api-client/types.ts +27 -1
- package/src/app/runtime/desktop-deeplink.ts +48 -15
- package/src/architecture/core-builtin-allowlist.json +1 -1
- package/src/architecture/dom-cell-metrics-allowlist.json +28 -0
- package/src/ask-gloom/pane-context.ts +23 -19
- package/src/ask-gloom/pane-request.ts +2 -2
- package/src/cli/commands/fred-series.ts +77 -11
- package/src/cli/commands/market.ts +77 -20
- package/src/cli/commands/overview.ts +29 -20
- package/src/cli/commands/ticker.ts +64 -11
- package/src/cli/desktop-pane-shot-view.ts +114 -0
- package/src/cli/desktop-pane-shot.ts +30 -31
- package/src/cli/entry.ts +7 -0
- package/src/cli/export-source.ts +43 -0
- package/src/cli/freshness.ts +18 -3
- package/src/cli/glossary.ts +17 -0
- package/src/cli/history-rows.ts +174 -13
- package/src/cli/known-gaps.ts +14 -3
- package/src/cli/listing-arg.ts +20 -2
- package/src/cli/listing-suggestion.ts +159 -0
- package/src/cli/native-smoke.ts +13 -0
- package/src/cli/not-a-ticker.ts +13 -1
- package/src/cli/pane-functions/capabilities.ts +16 -8
- package/src/cli/pane-functions/dom.ts +28 -13
- package/src/cli/pane-functions/function-help.ts +51 -6
- package/src/cli/pane-functions/headless.ts +26 -7
- package/src/cli/pane-functions/index.ts +46 -3
- package/src/cli/pane-functions/report-notices.ts +6 -3
- package/src/cli/pane-functions/report.ts +16 -2
- package/src/cli/pane-functions/resolver.ts +7 -4
- package/src/cli/report-tables.ts +31 -7
- package/src/cli/result.ts +54 -11
- package/src/cli/symbol-rows.ts +18 -1
- package/src/components/chart/composite/axis-overlays.tsx +2 -0
- package/src/components/chart/composite/composite-chart.tsx +4 -21
- package/src/components/chart/composite/legend.tsx +10 -6
- package/src/components/chart/composite/price-axis-labels.tsx +1 -0
- package/src/components/command-bar/list/model.ts +2 -0
- package/src/components/command-bar/pane-templates/items.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/actions.ts +1 -0
- package/src/components/command-bar/routes/ticker-search/results.ts +3 -1
- package/src/components/command-bar/workflow/ops.ts +8 -97
- package/src/components/command-bar/workflow/pane-reuse.ts +177 -0
- package/src/components/data-table/view.tsx +1 -0
- package/src/components/feed-data-table/stack-view.tsx +18 -42
- package/src/components/layout/missing-pane.tsx +2 -0
- package/src/components/layout/shell/index.tsx +1 -0
- package/src/components/layout/shell/pane/ask.ts +12 -7
- package/src/components/layout/shell/pane/capture.ts +8 -34
- package/src/components/markdown-text.tsx +37 -6
- package/src/components/reader/article-reader.tsx +91 -266
- package/src/components/reader/detail-reader.tsx +210 -0
- package/src/components/reader/reader-text.tsx +239 -0
- package/src/components/table-view-shared.tsx +8 -4
- package/src/components/ticker/badge/list.tsx +15 -4
- package/src/components/ui/choice-dialog.tsx +3 -0
- package/src/components/ui/data-table/types.ts +7 -0
- package/src/components/ui/detail-scroll-body.tsx +31 -4
- package/src/components/ui/display.tsx +46 -1
- package/src/components/ui/icon.tsx +11 -1
- package/src/components/ui/list-view.tsx +3 -1
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +9 -1
- package/src/components/ui/select-button.tsx +3 -0
- package/src/data/config/store/fold-panes.ts +202 -11
- package/src/data/config/store/migrations.ts +13 -1
- package/src/docs/snapshot.json +29 -0
- package/src/i18n/es.ts +14 -0
- package/src/i18n/ja.ts +14 -0
- package/src/i18n/ko.ts +14 -0
- package/src/i18n/zh-cn.ts +14 -0
- package/src/i18n/zh-tw.ts +14 -0
- package/src/layout/desk-keys.ts +7 -0
- package/src/layout/desks.ts +2 -2
- package/src/market-data/market/status.ts +67 -6
- package/src/market-data/quotes/contributions.ts +15 -0
- package/src/market-data/quotes/regular-session-valuation.ts +131 -0
- package/src/market-data/quotes/resolution.ts +4 -0
- package/src/market-data/snapshot-provider.ts +6 -3
- package/src/notifications/app-badge.ts +30 -0
- package/src/plugins/builtin/account-management/calendar-tab.tsx +1 -1
- package/src/plugins/builtin/account-management/form-components.tsx +53 -1
- package/src/plugins/builtin/account-management/keyboard.ts +5 -0
- package/src/plugins/builtin/account-management/model.ts +27 -0
- package/src/plugins/builtin/account-management/pane.tsx +62 -2
- package/src/plugins/builtin/alerts/cli.ts +6 -4
- package/src/plugins/builtin/analytics/index.tsx +1 -1
- package/src/plugins/builtin/awards/pane.tsx +29 -18
- package/src/plugins/builtin/backtest/pane.tsx +2 -1
- package/src/plugins/builtin/bond-calculator/index.tsx +3 -5
- package/src/plugins/builtin/buildout/detail/company.tsx +7 -4
- package/src/plugins/builtin/builtin-plugin-meta.ts +5 -4
- package/src/plugins/builtin/catalysts/model.ts +92 -10
- package/src/plugins/builtin/catalysts/pane.tsx +6 -4
- package/src/plugins/builtin/cds/index.tsx +21 -30
- package/src/plugins/builtin/changelog/index.tsx +11 -12
- package/src/plugins/builtin/chart-composer/headless-schema.ts +2 -0
- package/src/plugins/builtin/chart-composer/headless.ts +87 -5
- package/src/plugins/builtin/chart-composer/pane.tsx +3 -2
- package/src/plugins/builtin/chart-composer/session-history.ts +20 -5
- package/src/plugins/builtin/chat/command-bar.ts +6 -8
- package/src/plugins/builtin/chat/content/composer-runtime.ts +2 -2
- package/src/plugins/builtin/chat/content/composer.tsx +13 -4
- package/src/plugins/builtin/chat/content/footer.tsx +15 -5
- package/src/plugins/builtin/chat/content/handle-prompt.tsx +326 -0
- package/src/plugins/builtin/chat/content/index.tsx +113 -9
- package/src/plugins/builtin/chat/content/layout-metrics.ts +4 -0
- package/src/plugins/builtin/chat/content/open-targets.ts +20 -4
- package/src/plugins/builtin/chat/content/scroll.ts +6 -1
- package/src/plugins/builtin/chat/content/transcript.tsx +11 -2
- package/src/plugins/builtin/chat/ghost-user.ts +1 -1
- package/src/plugins/builtin/chat/handle-prompt.ts +128 -0
- package/src/plugins/builtin/chat/index.tsx +44 -21
- package/src/plugins/builtin/chat/layout.ts +7 -14
- package/src/plugins/builtin/chat/message/desktop.tsx +59 -11
- package/src/plugins/builtin/chat/message/header.tsx +69 -21
- package/src/plugins/builtin/chat/message/pane-chip.tsx +223 -0
- package/src/plugins/builtin/chat/message/person-icons.tsx +53 -0
- package/src/plugins/builtin/chat/message/profile-popover.tsx +73 -9
- package/src/plugins/builtin/chat/message/render-state.ts +15 -2
- package/src/plugins/builtin/chat/message/terminal.tsx +33 -8
- package/src/plugins/builtin/chat/message/types.ts +3 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +149 -0
- package/src/plugins/builtin/chat/pane-links/open.ts +29 -0
- package/src/plugins/builtin/chat/pane-links/previews.ts +119 -0
- package/src/plugins/builtin/chat/pane-state.ts +5 -8
- package/src/plugins/builtin/chat/people.ts +202 -0
- package/src/plugins/builtin/chat/profile-popover.ts +18 -2
- package/src/plugins/builtin/chat/status-widget.tsx +3 -8
- package/src/plugins/builtin/chat/unread-badge.ts +65 -0
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +9 -6
- package/src/plugins/builtin/clinical-trials/client.ts +26 -1
- package/src/plugins/builtin/clinical-trials/index.tsx +10 -2
- package/src/plugins/builtin/clinical-trials/model.ts +269 -0
- package/src/plugins/builtin/clinical-trials/pane.tsx +247 -62
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +156 -0
- package/src/plugins/builtin/cloud/askg/attached-pane.tsx +3 -3
- package/src/plugins/builtin/cloud/askg/pane-attachment.ts +3 -3
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +1 -0
- package/src/plugins/builtin/comment-letters/pane.tsx +30 -11
- package/src/plugins/builtin/commodities-energy/index.tsx +179 -0
- package/src/plugins/builtin/commodity-balances/client.ts +208 -0
- package/src/plugins/builtin/commodity-balances/headless.ts +66 -0
- package/src/plugins/builtin/commodity-balances/index.tsx +32 -0
- package/src/plugins/builtin/commodity-balances/model.ts +261 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +227 -0
- package/src/plugins/builtin/composite-plugins.ts +26 -1
- package/src/plugins/builtin/credit-boards/index.tsx +21 -42
- package/src/plugins/builtin/credit-boards/model.ts +0 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +10 -19
- package/src/plugins/builtin/credit-documents/index.tsx +27 -10
- package/src/plugins/builtin/debt-maturities/index.tsx +3 -17
- package/src/plugins/builtin/debug/index.tsx +2 -2
- package/src/plugins/builtin/dividend-yield/client.ts +3 -1
- package/src/plugins/builtin/dividend-yield/headless.ts +3 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +3 -1
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/docs/blocks.tsx +4 -3
- package/src/plugins/builtin/docs/pane.tsx +16 -8
- package/src/plugins/builtin/earnings/index.tsx +2 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +2 -12
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +36 -2
- package/src/plugins/builtin/equity-screener/model.ts +121 -68
- package/src/plugins/builtin/executives/pane.tsx +3 -1
- package/src/plugins/builtin/filing-events/index.tsx +20 -0
- package/src/plugins/builtin/fixed-income/index.tsx +46 -0
- package/src/plugins/builtin/fixed-income/model.ts +27 -0
- package/src/plugins/builtin/fixed-income/pane.tsx +162 -0
- package/src/plugins/builtin/fixed-income/shortcut.ts +52 -0
- package/src/plugins/builtin/futures/index.tsx +24 -7
- package/src/plugins/builtin/futures/series.ts +23 -2
- package/src/plugins/builtin/futures/use-front-returns.ts +3 -2
- package/src/plugins/builtin/futures-curve/pane.tsx +6 -4
- package/src/plugins/builtin/help/shortcut-table.tsx +4 -2
- package/src/plugins/builtin/insider/detail.tsx +20 -12
- package/src/plugins/builtin/insider/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/index.tsx +3 -0
- package/src/plugins/builtin/iv-history/screen-pane.tsx +13 -5
- package/src/plugins/builtin/jodi/client.ts +220 -0
- package/src/plugins/builtin/jodi/headless.ts +63 -0
- package/src/plugins/builtin/jodi/index.tsx +32 -0
- package/src/plugins/builtin/jodi/model.ts +75 -0
- package/src/plugins/builtin/jodi/pane.tsx +141 -0
- package/src/plugins/builtin/market-heatmap/evidence.ts +13 -4
- package/src/plugins/builtin/market-heatmap/headless.ts +3 -3
- package/src/plugins/builtin/market-heatmap/index.tsx +41 -23
- package/src/plugins/builtin/market-heatmap/live.ts +2 -1
- package/src/plugins/builtin/market-heatmap/portfolio.ts +67 -21
- package/src/plugins/builtin/market-movers/table.ts +2 -2
- package/src/plugins/builtin/market-valuation/align.ts +10 -9
- package/src/plugins/builtin/mna/distress/model.ts +52 -1
- package/src/plugins/builtin/mna/distress/pane.tsx +17 -12
- package/src/plugins/builtin/mna/index.tsx +2 -0
- package/src/plugins/builtin/news/wire/index.tsx +2 -2
- package/src/plugins/builtin/news/wire/industry-pane.tsx +28 -14
- package/src/plugins/builtin/news/wire/letters/client.ts +108 -0
- package/src/plugins/builtin/news/wire/letters/model.ts +69 -0
- package/src/plugins/builtin/news/wire/letters/pane.tsx +155 -0
- package/src/plugins/builtin/news/wire/mutes.ts +2 -1
- package/src/plugins/builtin/news/wire/news/query-presets.ts +4 -1
- package/src/plugins/builtin/openfda/client.ts +123 -15
- package/src/plugins/builtin/openfda/model.ts +163 -0
- package/src/plugins/builtin/openfda/pane.tsx +109 -65
- package/src/plugins/builtin/openfda/types.ts +25 -0
- package/src/plugins/builtin/options/view.tsx +1 -1
- package/src/plugins/builtin/pe-band/model.ts +3 -2
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +1 -1
- package/src/plugins/builtin/plugin-module.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +1 -1
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +19 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +2 -2
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +39 -0
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +18 -5
- package/src/plugins/builtin/portfolio-list/live-valuation.ts +25 -13
- package/src/plugins/builtin/power/evidence.ts +32 -3
- package/src/plugins/builtin/power/gas-client.ts +110 -0
- package/src/plugins/builtin/power/gas-model.ts +194 -0
- package/src/plugins/builtin/power/gas.tsx +145 -0
- package/src/plugins/builtin/power/headless.ts +4 -1
- package/src/plugins/builtin/power/index.tsx +19 -9
- package/src/plugins/builtin/power/pane.tsx +2 -3
- package/src/plugins/builtin/power/prices-headless.ts +75 -7
- package/src/plugins/builtin/power/prices.tsx +36 -13
- package/src/plugins/builtin/primary-dealers/client.ts +108 -0
- package/src/plugins/builtin/primary-dealers/headless.ts +53 -0
- package/src/plugins/builtin/primary-dealers/index.tsx +17 -0
- package/src/plugins/builtin/primary-dealers/model.ts +206 -0
- package/src/plugins/builtin/primary-dealers/pane.tsx +149 -0
- package/src/plugins/builtin/relative-rotation/index.tsx +3 -0
- package/src/plugins/builtin/research/analyst-model.ts +9 -5
- package/src/plugins/builtin/research/headless-schema.ts +2 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +68 -32
- package/src/plugins/builtin/research/relative-valuation-model.ts +45 -24
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +39 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +8 -1
- package/src/plugins/builtin/sec/filing-display.tsx +3 -36
- package/src/plugins/builtin/sec/index.tsx +131 -43
- package/src/plugins/builtin/sec/model.ts +148 -2
- package/src/plugins/builtin/sec/row-summaries.ts +105 -0
- package/src/plugins/builtin/sectors/index.tsx +2 -0
- package/src/plugins/builtin/sectors/sector-model.ts +2 -2
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/locked-rows.tsx +1 -1
- package/src/plugins/builtin/shared/use-quote-board.ts +179 -43
- package/src/plugins/builtin/shared/wall-teaser.tsx +2 -2
- package/src/plugins/builtin/shared/zip-text.ts +46 -0
- package/src/plugins/builtin/short-interest/watch-model.ts +11 -2
- package/src/plugins/builtin/themes/model.ts +2 -2
- package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +11 -0
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +81 -19
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/headless.ts +2 -2
- package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +7 -5
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +8 -6
- package/src/plugins/builtin/time-sales/headless.ts +22 -0
- package/src/plugins/builtin/trace-bonds/client.ts +196 -0
- package/src/plugins/builtin/trace-bonds/headless.ts +69 -0
- package/src/plugins/builtin/trace-bonds/index.tsx +33 -0
- package/src/plugins/builtin/trace-bonds/model.ts +115 -0
- package/src/plugins/builtin/trace-bonds/pane.tsx +191 -0
- package/src/plugins/builtin/treasury-auctions/index.tsx +33 -47
- package/src/plugins/builtin/treasury-daily/client.ts +96 -0
- package/src/plugins/builtin/treasury-daily/headless.ts +69 -0
- package/src/plugins/builtin/treasury-daily/index.tsx +17 -0
- package/src/plugins/builtin/treasury-daily/model.ts +69 -0
- package/src/plugins/builtin/treasury-daily/pane.tsx +140 -0
- package/src/plugins/builtin/world-venue-map/entity-detail.tsx +15 -2
- package/src/plugins/builtin/world-venue-map/map.tsx +1 -0
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/catalog-browser.ts +25 -2
- package/src/plugins/marketplace/model.ts +57 -21
- package/src/renderers/dom/build-assets.ts +14 -5
- package/src/renderers/dom/data-table/index.tsx +5 -3
- package/src/renderers/dom/desktop/controls.tsx +2 -1
- package/src/renderers/dom/desktop/icons.tsx +25 -0
- package/src/renderers/dom/desktop/list-view.tsx +1 -1
- package/src/renderers/dom/desktop/menu.tsx +1 -1
- package/src/renderers/dom/desktop/query-bar.tsx +3 -1
- package/src/renderers/dom/host/chart-surface.tsx +7 -2
- package/src/renderers/dom/host/resize-rows.ts +111 -0
- package/src/renderers/dom/host/sticky-scroll.ts +22 -3
- package/src/renderers/dom/host/style.ts +14 -0
- package/src/renderers/dom/host/surface-3d.tsx +2 -1
- package/src/renderers/dom/host/surface-fit.ts +10 -6
- package/src/renderers/dom/host/text.tsx +2 -2
- package/src/renderers/dom/styles.css +63 -6
- package/src/renderers/electrobun/bun/desktop/app-badge.ts +37 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +8 -0
- package/src/renderers/electrobun/bun/desktop/macos-dock-badge.ts +37 -0
- package/src/renderers/electrobun/bun/desktop/macos-dock-icon.ts +2 -40
- package/src/renderers/electrobun/bun/desktop/macos-objc.ts +41 -0
- package/src/renderers/electrobun/bun/tui-entry.ts +18 -1
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/app-badge-host.ts +17 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +8 -3
- package/src/renderers/electrobun/view/main.tsx +2 -0
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +5 -1
- package/src/series/validate.ts +1 -1
- package/src/shares/api.ts +5 -1
- package/src/shares/location.ts +31 -0
- package/src/shares/open.ts +26 -0
- package/src/shares/pane-image.ts +18 -6
- package/src/shares/personal-pane.ts +42 -54
- package/src/sources/gloomberb-cloud/index.ts +7 -2
- package/src/sources/gloomberb-cloud/normalizers.ts +5 -0
- package/src/sources/history-coverage.ts +22 -4
- package/src/sources/history-result.ts +3 -1
- package/src/sources/provider-errors.ts +2 -1
- package/src/sources/provider-router/batches.ts +18 -2
- package/src/sources/provider-router/index.ts +2 -1
- package/src/state/app/context/index.tsx +3 -2
- package/src/test-support/builtin-panes.ts +31 -0
- package/src/tickers/listing-venues.ts +22 -0
- package/src/tickers/search/index.ts +7 -2
- package/src/tickers/search/ranking.ts +19 -0
- package/src/time-series/chart-data.ts +2 -0
- package/src/time-series/parsed-history-cache.ts +41 -11
- package/src/time-series/resolve.ts +47 -15
- package/src/types/config.ts +1 -1
- package/src/types/data-provider.ts +13 -1
- package/src/types/financials.ts +13 -0
- package/src/types/headless.ts +22 -0
- package/src/types/plugin.ts +33 -6
- package/src/types/price-history.ts +2 -0
- package/src/ui/host.tsx +6 -0
- package/src/utils/crypto-pair.ts +1 -1
- package/src/utils/currency-pair.ts +66 -0
- package/src/utils/decimal-comma.ts +57 -0
- package/src/utils/guards.ts +25 -0
- package/src/utils/local-utc-offset.ts +37 -0
- package/src/utils/plugin-proxy-hosts.json +8 -1
- package/src/components/chart/composite/crosshair-readout.tsx +0 -150
- package/src/plugins/builtin/chat/message/ai-badge.tsx +0 -18
- package/src/plugins/builtin/credit-boards/pane.tsx +0 -30
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import { comparablePriceEarnings, notMeaningfulMultiples } from "../../../utils/price-earnings";
|
|
2
2
|
import { selectMarketCapitalization } from "../../../utils/market-capitalization";
|
|
3
3
|
import { reportedEnterpriseValue } from "../../../utils/fundamentals";
|
|
4
|
+
import { getExtendedSessionDisplay, getQuoteSessionFields } from "../../../market-data/market/status";
|
|
5
|
+
import { atRegularSessionPrice, movePriceDerivedFigures } from "../../../market-data/quotes/regular-session-valuation";
|
|
4
6
|
import type { Quote, TickerFinancials } from "../../../types/financials";
|
|
5
7
|
|
|
6
8
|
export const RELATIVE_VALUATION_STALE_QUOTE_NOTICE = "Quote stale: quote-based values unavailable";
|
|
7
9
|
export const RELATIVE_VALUATION_STALE_FUNDAMENTALS_NOTICE = "Fundamentals stale: retained values may be out of date";
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
/**
|
|
12
|
+
* A peer's row. Last, its change and the extended print are the quote's
|
|
13
|
+
* sessions as `quote` reads them (getQuoteSessionFields): after the close,
|
|
14
|
+
* the close and its move, and the pre-market or after-hours print measured
|
|
15
|
+
* from it. Every figure measured at the price (market cap, P/E, forward P/E
|
|
16
|
+
* and through the cap the FCF yield) is at that regular session price too.
|
|
17
|
+
*/
|
|
18
|
+
export function relativeValuationValues(snapshot: TickerFinancials | null) {
|
|
19
|
+
const reported = snapshot?.quote;
|
|
20
|
+
const financials = snapshot ? atRegularSessionPrice(snapshot) : null;
|
|
10
21
|
const quote = financials?.quote;
|
|
11
22
|
const fundamentals = financials?.fundamentals;
|
|
12
23
|
const quoteStale = quote?.stale === true;
|
|
24
|
+
const session = getQuoteSessionFields(quoteStale ? null : quote);
|
|
25
|
+
// An overnight price that is the session's bid and ask midpoint, nothing having traded in it.
|
|
26
|
+
const extendedMark = !quoteStale && getExtendedSessionDisplay(quote)?.mark === true;
|
|
13
27
|
const capitalization = selectMarketCapitalization(quoteStale ? undefined : quote, fundamentals);
|
|
14
28
|
const enterpriseValue = reportedEnterpriseValue(fundamentals);
|
|
15
29
|
// A zero or non-finite enterprise value is the source's gap, and the ratio it serves beside it comes from the same observation.
|
|
@@ -27,21 +41,21 @@ export function relativeValuationValues(financials: TickerFinancials | null) {
|
|
|
27
41
|
...(forwardNotMeaningful ? { forwardPE: forwardNotMeaningful } : {}),
|
|
28
42
|
};
|
|
29
43
|
return {
|
|
30
|
-
|
|
44
|
+
...session,
|
|
45
|
+
extendedMark,
|
|
31
46
|
quoteStale: quote?.stale ?? null,
|
|
32
47
|
quoteAsOf: quote && Number.isFinite(quote.lastUpdated) && quote.lastUpdated > 0 ? quote.lastUpdated : null,
|
|
33
|
-
//
|
|
34
|
-
reportedQuote:
|
|
35
|
-
price:
|
|
36
|
-
currency:
|
|
37
|
-
providerId:
|
|
48
|
+
// The observation as sent, apart from the values used for comparison: a rejected stale quote, or the print the source priced.
|
|
49
|
+
reportedQuote: reported ? {
|
|
50
|
+
price: reported.price, changePercent: reported.changePercent, marketCap: reported.marketCap ?? null,
|
|
51
|
+
currency: reported.currency, lastUpdated: reported.lastUpdated, stale: reported.stale ?? null,
|
|
52
|
+
providerId: reported.providerId ?? null, dataSource: reported.dataSource ?? null,
|
|
38
53
|
} : null,
|
|
39
54
|
fundamentalsProvenance: fundamentals ? {
|
|
40
55
|
source: fundamentals.source ?? null, retrievedAt: fundamentals.fetchedAt ?? null,
|
|
41
56
|
stale: fundamentals.stale ?? null,
|
|
42
57
|
} : null,
|
|
43
58
|
currency: quote?.currency ?? null,
|
|
44
|
-
changePercent: quoteStale ? null : quote?.changePercent ?? null,
|
|
45
59
|
marketCap: capitalization?.value ?? null,
|
|
46
60
|
marketCapCurrency: capitalization?.currency ?? null,
|
|
47
61
|
marketCapProvenance: capitalization?.provenance ?? null,
|
|
@@ -68,17 +82,27 @@ function scaled(value: number | undefined, ratio: number): number | undefined {
|
|
|
68
82
|
return value != null && Number.isFinite(value) ? value * ratio : value;
|
|
69
83
|
}
|
|
70
84
|
|
|
85
|
+
/** What a quote says about its sessions; a newer quote's replaces the snapshot's whole, so a close never pairs with another day's print. */
|
|
86
|
+
const SESSION_FIELDS = [
|
|
87
|
+
"marketState", "changeSessionDate", "regularClose", "regularCloseSessionDate", "regularChange", "regularChangePercent",
|
|
88
|
+
"preMarketPrice", "preMarketChange", "preMarketChangePercent", "postMarketPrice", "postMarketChange", "postMarketChangePercent",
|
|
89
|
+
"extendedSessionPrint", "overnightPrice", "overnightChange", "overnightChangePercent", "overnightPriceKind",
|
|
90
|
+
] as const satisfies readonly (keyof Quote)[];
|
|
91
|
+
|
|
71
92
|
/**
|
|
72
|
-
* The snapshot with a newer quote laid over it
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
93
|
+
* The snapshot with a newer quote laid over it, its sessions included, so
|
|
94
|
+
* relativeValuationValues reads the newer quote's regular session and
|
|
95
|
+
* extended print. Fundamentals stay from the snapshot. Values measured
|
|
96
|
+
* against the price move by live price / snapshot price (and from there to
|
|
97
|
+
* the regular session price in relativeValuationValues), but only from a
|
|
98
|
+
* price they were measured at: the quote's market cap (and through it the
|
|
99
|
+
* FCF yield) when the snapshot quote is not stale, and the fundamentals'
|
|
100
|
+
* price-derived figures (movePriceDerivedFigures: the P/E multiples, the cap,
|
|
101
|
+
* and the equity part of the enterprise value and EV/Sales) when the
|
|
102
|
+
* fundamentals are not stale either. A stale snapshot quote drops its cap, so
|
|
103
|
+
* the row falls back to the fundamentals' cap as it would without the live
|
|
104
|
+
* quote. A quote in another currency, or a stale one, leaves the snapshot
|
|
105
|
+
* alone.
|
|
82
106
|
*/
|
|
83
107
|
export function withLiveQuote(financials: TickerFinancials | null, live: Quote | null | undefined): TickerFinancials | null {
|
|
84
108
|
const base = financials?.quote;
|
|
@@ -91,6 +115,8 @@ export function withLiveQuote(financials: TickerFinancials | null, live: Quote |
|
|
|
91
115
|
price: live.price,
|
|
92
116
|
change: live.change,
|
|
93
117
|
changePercent: live.changePercent,
|
|
118
|
+
previousClose: live.previousClose ?? base.previousClose,
|
|
119
|
+
...Object.fromEntries(SESSION_FIELDS.map((key) => [key, live[key]])),
|
|
94
120
|
lastUpdated: live.lastUpdated,
|
|
95
121
|
stale: live.stale,
|
|
96
122
|
...(live.dataSource ? { dataSource: live.dataSource } : {}),
|
|
@@ -103,11 +129,6 @@ export function withLiveQuote(financials: TickerFinancials | null, live: Quote |
|
|
|
103
129
|
return {
|
|
104
130
|
...financials,
|
|
105
131
|
quote: { ...quote, marketCap: scaled(base.marketCap, ratio) },
|
|
106
|
-
fundamentals: fundamentals && fundamentals.stale !== true ?
|
|
107
|
-
...fundamentals,
|
|
108
|
-
trailingPE: scaled(fundamentals.trailingPE, ratio),
|
|
109
|
-
forwardPE: scaled(fundamentals.forwardPE, ratio),
|
|
110
|
-
marketCap: fundamentals.marketCapCurrency === live.currency ? scaled(fundamentals.marketCap, ratio) : fundamentals.marketCap,
|
|
111
|
-
} : fundamentals,
|
|
132
|
+
fundamentals: fundamentals && fundamentals.stale !== true ? movePriceDerivedFigures(fundamentals, live.currency, ratio) : fundamentals,
|
|
112
133
|
};
|
|
113
134
|
}
|
|
@@ -19,6 +19,7 @@ import type { QuoteSubscriptionTarget } from "../../../types/data-provider";
|
|
|
19
19
|
import type { TickerFinancials } from "../../../types/financials";
|
|
20
20
|
import { normalizeSymbol } from "../../../utils/exchanges";
|
|
21
21
|
import { colors, priceColor } from "../../../theme/colors";
|
|
22
|
+
import { extendedSessionStateLabel, type ExtendedSession } from "../../../market-data/market/status";
|
|
22
23
|
import { compareSortValues, nextHeaderSort, type SortDirection } from "../../../utils/sort-values";
|
|
23
24
|
import { formatCompact, formatCurrency, formatLevelPercent, formatNumber, formatPercent, formatPercentRaw } from "../../../utils/format";
|
|
24
25
|
import { parseDisplayDate } from "../../../utils/datetime-format";
|
|
@@ -30,7 +31,8 @@ import { useBoundTicker as useSymbolBinding } from "../shared/ticker-request";
|
|
|
30
31
|
import { useFxRatesMap } from "../../../market-data/hooks";
|
|
31
32
|
import { RELATIVE_VALUATION_STALE_FUNDAMENTALS_NOTICE, relativeValuationValues, withLiveQuote } from "./relative-valuation-model";
|
|
32
33
|
|
|
33
|
-
type
|
|
34
|
+
type ExtendedColumnId = "preMarket" | "afterHours" | "overnight";
|
|
35
|
+
type RelativeColumnId = "symbol" | "price" | "changePercent" | ExtendedColumnId | "marketCap" | "trailingPE" | "forwardPE" | "evSales" | "fcfYield" | "revenueGrowth" | "operatingMargin";
|
|
34
36
|
type RelativeColumn = DataTableColumn & { id: RelativeColumnId };
|
|
35
37
|
type RelativeRow = ReturnType<typeof relativeValuationValues> & {
|
|
36
38
|
symbol: string;
|
|
@@ -45,7 +47,24 @@ function relativeSymbolsFromPane(symbol: string | null, paneSettings: Record<str
|
|
|
45
47
|
return symbol ? [symbol] : [];
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
const EXTENDED_COLUMN_IDS: Record<ExtendedSession, ExtendedColumnId> = { PRE: "preMarket", POST: "afterHours", OVERNIGHT: "overnight" };
|
|
51
|
+
|
|
52
|
+
/** The extended sessions some peer has a print for, in the order their columns go. */
|
|
53
|
+
function extendedSessionsOf(rows: readonly RelativeRow[]): ExtendedSession[] {
|
|
54
|
+
return (["PRE", "POST", "OVERNIGHT"] as const).filter((session) => rows.some((row) => row.extendedSession === session));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A peer's move in the extended-session column `columnId`, from the close in LAST. */
|
|
58
|
+
function extendedMove(row: RelativeRow, columnId: ExtendedColumnId): number | null {
|
|
59
|
+
return row.extendedSession != null && EXTENDED_COLUMN_IDS[row.extendedSession] === columnId ? row.extendedChangePercent : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* LAST and CHG% are the regular session, as `quote` reads them; a PRE-MKT,
|
|
64
|
+
* AFTER-HRS or OVERNIGHT column, named as BI and MOST name it, follows CHG%
|
|
65
|
+
* for each session in `extendedSessions`.
|
|
66
|
+
*/
|
|
67
|
+
function buildRelativeColumns(baseCurrency: string, extendedSessions: readonly ExtendedSession[]): RelativeColumn[] {
|
|
49
68
|
const symbolWidth = 8;
|
|
50
69
|
const priceWidth = 10;
|
|
51
70
|
const pctWidth = 8;
|
|
@@ -55,6 +74,9 @@ function buildRelativeColumns(baseCurrency: string): RelativeColumn[] {
|
|
|
55
74
|
{ id: "symbol", label: "TICKER", width: symbolWidth, align: "left" },
|
|
56
75
|
{ id: "price", label: "LAST", width: priceWidth, align: "right" },
|
|
57
76
|
{ id: "changePercent", label: "CHG%", width: pctWidth, align: "right" },
|
|
77
|
+
...extendedSessions.map((session): RelativeColumn => ({
|
|
78
|
+
id: EXTENDED_COLUMN_IDS[session], label: extendedSessionStateLabel(session), width: 9, align: "right",
|
|
79
|
+
})),
|
|
58
80
|
{ id: "marketCap", label: `MCAP ${baseCurrency}`, width: capWidth, align: "right" },
|
|
59
81
|
{ id: "trailingPE", label: "P/E", width: metricWidth, align: "right" },
|
|
60
82
|
{ id: "forwardPE", label: "FWD", width: metricWidth, align: "right" },
|
|
@@ -74,7 +96,9 @@ interface RelativeSortPreference {
|
|
|
74
96
|
const DEFAULT_RELATIVE_SORT: RelativeSortPreference = { columnId: "marketCap", direction: "desc" };
|
|
75
97
|
|
|
76
98
|
function relativeSortValue(row: RelativeRow, columnId: RelativeColumnId): string | number | null {
|
|
77
|
-
|
|
99
|
+
if (columnId === "symbol") return row.symbol.toLocaleLowerCase();
|
|
100
|
+
if (columnId === "preMarket" || columnId === "afterHours" || columnId === "overnight") return extendedMove(row, columnId);
|
|
101
|
+
return row[columnId];
|
|
78
102
|
}
|
|
79
103
|
|
|
80
104
|
function sortRelativeRowIndices(
|
|
@@ -153,7 +177,12 @@ export function RelativeValuationPane({ focused, width, height }: PaneProps) {
|
|
|
153
177
|
};
|
|
154
178
|
}), [liveQuotes, peers]);
|
|
155
179
|
const fxRates = useFxRatesMap([baseCurrency, ...rows.map((row) => row.marketCapCurrency)]);
|
|
156
|
-
|
|
180
|
+
// A pre-market or after-hours print gets its own column when a peer has one.
|
|
181
|
+
const extendedSessionsKey = extendedSessionsOf(rows).join(",");
|
|
182
|
+
const columns = useMemo(
|
|
183
|
+
() => buildRelativeColumns(baseCurrency, extendedSessionsKey ? extendedSessionsKey.split(",") as ExtendedSession[] : []),
|
|
184
|
+
[baseCurrency, extendedSessionsKey],
|
|
185
|
+
);
|
|
157
186
|
|
|
158
187
|
const comparableRows = useMemo(() => rows.map((row) => ({
|
|
159
188
|
...row, marketCap: convertMarketCapitalization(row.marketCap, row.marketCapCurrency, baseCurrency, fxRates),
|
|
@@ -195,6 +224,12 @@ export function RelativeValuationPane({ focused, width, height }: PaneProps) {
|
|
|
195
224
|
return { text: row.price != null ? formatCurrency(row.price, row.currency ?? "USD") : "-", color: colors.text };
|
|
196
225
|
case "changePercent":
|
|
197
226
|
return { text: row.changePercent != null ? formatPercentRaw(row.changePercent) : "-", color: priceColor(row.changePercent ?? 0) };
|
|
227
|
+
case "preMarket":
|
|
228
|
+
case "afterHours":
|
|
229
|
+
case "overnight": {
|
|
230
|
+
const move = extendedMove(row, column.id);
|
|
231
|
+
return { text: move != null ? formatPercentRaw(move) : "-", value: move, color: move == null ? colors.textDim : priceColor(move) };
|
|
232
|
+
}
|
|
198
233
|
case "marketCap":
|
|
199
234
|
return { text: formatCompact(row.marketCap ?? undefined, { fixedDecimals: true }), color: colors.textDim };
|
|
200
235
|
case "trailingPE":
|
|
@@ -66,6 +66,12 @@ function buildColumns(width: number, dated: boolean): DataTableColumn[] {
|
|
|
66
66
|
];
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
function sideColor(side: ScannerFlowEvent["side"]): string {
|
|
70
|
+
if (side === "ask") return colors.positive;
|
|
71
|
+
if (side === "bid") return colors.negative;
|
|
72
|
+
return side === "mid" ? colors.text : colors.textDim;
|
|
73
|
+
}
|
|
74
|
+
|
|
69
75
|
// Stable table adapters so memoized rows survive feed ticks.
|
|
70
76
|
const eventKey = (event: ScannerFlowEvent) => event.id;
|
|
71
77
|
|
|
@@ -90,7 +96,8 @@ function renderCell(
|
|
|
90
96
|
case "expiry":
|
|
91
97
|
return { text: formatFlowExpiry(event.expiry), color: colors.textDim };
|
|
92
98
|
case "side":
|
|
93
|
-
|
|
99
|
+
// Bought at the ask reads green, sold at the bid red, as on every tape; the word stays.
|
|
100
|
+
return { text: formatFlowSide(event.side), color: sideColor(event.side) };
|
|
94
101
|
case "size":
|
|
95
102
|
return { text: formatCompact(event.size) };
|
|
96
103
|
case "premium":
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { colors } from "../../../theme/colors";
|
|
1
|
+
import { EmptyState } from "../../../components";
|
|
3
2
|
import { formatShortDate } from "../../../utils/datetime-format";
|
|
4
3
|
import { formatCompact, formatCurrency, formatNumber } from "../../../utils/format";
|
|
5
4
|
import { transactionTypeLabel, type InsiderTransaction } from "../insider/insider-data";
|
|
@@ -21,17 +20,8 @@ export function formatFilingFormLabel(form: string, fallback = "FORM 4"): string
|
|
|
21
20
|
return value ? `FORM ${value}` : fallback;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
export function renderFilingNotice(message: string
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<Box flexDirection="column" paddingX={1} paddingY={1}>
|
|
28
|
-
{lines.map((line, index) => (
|
|
29
|
-
<Box key={index} height={1}>
|
|
30
|
-
<Text fg={colors.textDim}>{line}</Text>
|
|
31
|
-
</Box>
|
|
32
|
-
))}
|
|
33
|
-
</Box>
|
|
34
|
-
);
|
|
23
|
+
export function renderFilingNotice(message: string) {
|
|
24
|
+
return <EmptyState title={message} />;
|
|
35
25
|
}
|
|
36
26
|
|
|
37
27
|
export function buildInsiderTransactionTitle(transaction: InsiderTransaction): string {
|
|
@@ -61,26 +51,3 @@ export function buildInsiderTransactionDetailBody(transaction: InsiderTransactio
|
|
|
61
51
|
];
|
|
62
52
|
return lines.join("\n");
|
|
63
53
|
}
|
|
64
|
-
|
|
65
|
-
function wrapNoticeLines(text: string, width: number): string[] {
|
|
66
|
-
const maxWidth = Math.max(width, 12);
|
|
67
|
-
const words = text.trim().split(/\s+/);
|
|
68
|
-
const lines: string[] = [];
|
|
69
|
-
let current = "";
|
|
70
|
-
|
|
71
|
-
for (const word of words) {
|
|
72
|
-
if (!current) {
|
|
73
|
-
current = word;
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (current.length + 1 + word.length <= maxWidth) {
|
|
77
|
-
current = `${current} ${word}`;
|
|
78
|
-
} else {
|
|
79
|
-
lines.push(current);
|
|
80
|
-
current = word;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (current) lines.push(current);
|
|
85
|
-
return lines;
|
|
86
|
-
}
|
|
@@ -6,16 +6,21 @@ import { getSharedMarketDataCoordinator } from "../../../market-data/coordinator
|
|
|
6
6
|
import { useShortcut } from "../../../react/input";
|
|
7
7
|
import { isPlainKey } from "../../../utils/keyboard";
|
|
8
8
|
import { instrumentFromTicker } from "../../../market-data/request-types";
|
|
9
|
-
import { useDebouncedPluginPaneState } from "../../runtime";
|
|
9
|
+
import { useDebouncedPluginPaneState, usePluginPaneState } from "../../runtime";
|
|
10
10
|
import { usePaneSettingValue } from "../../../state/app/context";
|
|
11
11
|
import type { ScrollBoxRenderable } from "../../../ui";
|
|
12
|
-
import { EmptyState, FeedDataTableStackView, Spinner,
|
|
12
|
+
import { EmptyState, FeedDataTableStackView, QueryBar, Spinner, TickerBadgeList, usePaneNoticeFooter, useTableLoadMore, type FeedDataTableItem, type PaneHint, type QueryBarFilter } from "../../../components";
|
|
13
|
+
import { displayWidth } from "../../../utils/format";
|
|
14
|
+
import type { DetailFact } from "../../../components/reader/detail-reader";
|
|
15
|
+
import type { ThemeColors } from "../../../theme/colors";
|
|
16
|
+
import { useThemeColors } from "../../../theme/theme-context";
|
|
17
|
+
import { useOpenDescription } from "../shared/open-description";
|
|
18
|
+
import { toneColor } from "../shared/research-cells";
|
|
13
19
|
import { mayBeUsEquityOrFundTicker, mayBeUsEquityTicker, secFilingItemCodes } from "../../../utils/sec";
|
|
14
|
-
import { parseForm4Xml
|
|
20
|
+
import { parseForm4Xml } from "../insider/insider-data";
|
|
15
21
|
import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
|
|
16
22
|
import {
|
|
17
23
|
formatFilingMetaDate,
|
|
18
|
-
buildInsiderTransactionTitle,
|
|
19
24
|
buildInsiderTransactionDetailBody,
|
|
20
25
|
renderFilingNotice,
|
|
21
26
|
} from "./filing-display";
|
|
@@ -37,6 +42,11 @@ import { SignInWall } from "../cloud/auth-actions";
|
|
|
37
42
|
import { ETF_FILING_FORMS, ETF_FORMS_SETTING, filterFilingsByForms, parseFormsSetting, SEC_FILING_FETCH_LIMIT } from "./forms";
|
|
38
43
|
import { createSecHeadless, fundFilingsEmpty, secHeadless } from "./headless";
|
|
39
44
|
import {
|
|
45
|
+
ALL_FORMS,
|
|
46
|
+
COMPANY_FORM_GROUPS,
|
|
47
|
+
filingTone,
|
|
48
|
+
filterFilingsByGroup,
|
|
49
|
+
FUND_FORM_GROUPS,
|
|
40
50
|
getFilingDisplayTitle,
|
|
41
51
|
getFilingColumnText,
|
|
42
52
|
getFormDescription,
|
|
@@ -45,6 +55,7 @@ import {
|
|
|
45
55
|
secIssuerLabel,
|
|
46
56
|
secReportedAcceptance,
|
|
47
57
|
} from "./model";
|
|
58
|
+
import { rowSummary, rowSummaryTarget } from "./row-summaries";
|
|
48
59
|
import { usePaneTickerIdentity } from "../../../state/hooks/pane-ticker";
|
|
49
60
|
import { createSecFilingSearchProvider, resetSecFilingFocusRequests, useSecFilingFocusRequest } from "./command-bar-search";
|
|
50
61
|
|
|
@@ -53,6 +64,8 @@ export { secHeadless } from "./headless";
|
|
|
53
64
|
const SEC_FILING_PAGE_SIZE = 50;
|
|
54
65
|
const NO_FILINGS: SecFilingItem[] = [];
|
|
55
66
|
const OWNERSHIP_FORMS = new Set(["3", "4", "5"]);
|
|
67
|
+
/** At most this many rows on screen have their insider forms and fund flow notices read for their row text. */
|
|
68
|
+
const OWNERSHIP_ROWS_READ = 40;
|
|
56
69
|
|
|
57
70
|
function formatFiledAt(filing: SecFilingItem): string {
|
|
58
71
|
return formatFilingMetaDate(filing.filingDate);
|
|
@@ -122,16 +135,6 @@ function buildDetailBodyWithDocuments({
|
|
|
122
135
|
return lines.join("\n");
|
|
123
136
|
}
|
|
124
137
|
|
|
125
|
-
function buildForm4Preview(content: string | null): string | null {
|
|
126
|
-
if (!content) return null;
|
|
127
|
-
const transactions = parseForm4Xml(content);
|
|
128
|
-
const tx = transactions[0];
|
|
129
|
-
if (!tx) return null;
|
|
130
|
-
if (transactions.length === 1) return `${tx.reportedName} — ${buildInsiderTransactionTitle(tx)}`;
|
|
131
|
-
const types = [...new Set(transactions.map((transaction) => transactionTypeLabel(transaction.transactionType)))];
|
|
132
|
-
return `${tx.reportedName} — ${transactions.length} transactions: ${types.join(", ")}`;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
138
|
function buildForm4Detail(content: string | null, filing: SecFilingItem): string {
|
|
136
139
|
if (!content) return buildDetailBody(filing);
|
|
137
140
|
const transactions = parseForm4Xml(content);
|
|
@@ -149,6 +152,9 @@ function buildForm4Detail(content: string | null, filing: SecFilingItem): string
|
|
|
149
152
|
}
|
|
150
153
|
|
|
151
154
|
function toFeedItems(
|
|
155
|
+
colors: ThemeColors,
|
|
156
|
+
issuerTicker: { symbol: string; open: () => void } | null,
|
|
157
|
+
showIssuer: boolean,
|
|
152
158
|
filings: SecFilingItem[],
|
|
153
159
|
selectedAccessionNumber: string | undefined,
|
|
154
160
|
contentCache: Map<string, string | null>,
|
|
@@ -169,10 +175,10 @@ function toFeedItems(
|
|
|
169
175
|
? `${buildDetailBody(filing)}\n\nReadable filing content was not available for this document.`
|
|
170
176
|
: buildDetailBody(filing);
|
|
171
177
|
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
178
|
+
// An insider form read once says who traded and how, a fund's 24F-2NT its
|
|
179
|
+
// year's flows; the row says it too.
|
|
180
|
+
const summaryTarget = rowSummaryTarget(filing);
|
|
181
|
+
const readSummary = summaryTarget ? rowSummary(filing.form, contentCache.get(summaryTarget.accessionNumber)) : null;
|
|
176
182
|
const form4Detail = isOwnership && hasFetchedContent
|
|
177
183
|
? buildForm4Detail(fetchedContent ?? null, filing)
|
|
178
184
|
: null;
|
|
@@ -197,13 +203,28 @@ function toFeedItems(
|
|
|
197
203
|
const enrichedTitle = formDesc
|
|
198
204
|
? `${displayTitle} — ${formDesc}`
|
|
199
205
|
: displayTitle;
|
|
206
|
+
const summary = readSummary?.title ?? getFilingColumnText(filing);
|
|
207
|
+
const items = secFilingItemCodes(filing.items);
|
|
208
|
+
const facts: DetailFact[] = [
|
|
209
|
+
{ label: "Issuer", value: filing.companyName || "Name unavailable" },
|
|
210
|
+
...(issuerTicker ? [{ label: "Ticker", value: issuerTicker.symbol, link: { label: `Open ${issuerTicker.symbol}`, onOpen: issuerTicker.open } }] : []),
|
|
211
|
+
{ label: "CIK", value: filing.cik },
|
|
212
|
+
{ label: "Form", value: formDesc ? `${filing.form} · ${formDesc}` : filing.form, color: toneColor(filingTone(filing, readSummary?.direction), colors) },
|
|
213
|
+
{ label: "Filed", value: formatFiledAt(filing) },
|
|
214
|
+
...(acceptedAt ? [{ label: "SEC-reported acceptance", value: acceptedAt }] : []),
|
|
215
|
+
...(items ? [{ label: "Items", value: items }] : []),
|
|
216
|
+
{ label: "Accession", value: filing.accessionNumber },
|
|
217
|
+
];
|
|
200
218
|
|
|
201
219
|
return {
|
|
202
220
|
id: filing.accessionNumber,
|
|
203
221
|
eyebrow: filing.form,
|
|
204
|
-
|
|
222
|
+
eyebrowColor: toneColor(filingTone(filing, readSummary?.direction), colors),
|
|
223
|
+
// A ticker shared by several filers names the filer on each row.
|
|
224
|
+
title: showIssuer && filing.companyName ? `${filing.companyName}: ${summary}` : summary,
|
|
205
225
|
timestamp: filing.filingDate,
|
|
206
226
|
detailTitle: enrichedTitle,
|
|
227
|
+
detailFacts: facts,
|
|
207
228
|
detailMeta: [
|
|
208
229
|
secIssuerLabel(filing),
|
|
209
230
|
`Filed ${formatFiledAt(filing)}`,
|
|
@@ -218,8 +239,11 @@ function toFeedItems(
|
|
|
218
239
|
});
|
|
219
240
|
}
|
|
220
241
|
|
|
221
|
-
|
|
242
|
+
/** `paneType` is set for the standalone pane and absent in the DES tab, whose header already names the security. */
|
|
243
|
+
function SecView({ width, height, focused, paneType }: { width: number; height: number; focused: boolean; paneType?: string }) {
|
|
222
244
|
const { ticker } = usePaneTickerIdentity();
|
|
245
|
+
const colors = useThemeColors();
|
|
246
|
+
const openDescription = useOpenDescription();
|
|
223
247
|
const selectionKey = `selectedIdx:${ticker?.metadata.ticker ?? "none"}`;
|
|
224
248
|
const [selectedIdx, setSelectedIdx] = useDebouncedPluginPaneState<number>(selectionKey, 0);
|
|
225
249
|
// The open filing is what the pane shows, so it is pane state: it restores
|
|
@@ -251,10 +275,22 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
|
|
|
251
275
|
const issuerFilings = useResolvedEntryValue(filingsEntry) ?? NO_FILINGS;
|
|
252
276
|
// The filter runs over every filing the service returned for the issuer,
|
|
253
277
|
// the same list the unfiltered pane shows, not over a first page of it.
|
|
254
|
-
const
|
|
278
|
+
const formFilings = useMemo(
|
|
255
279
|
() => (forms ? filterFilingsByForms(issuerFilings, forms) : issuerFilings),
|
|
256
280
|
[forms, issuerFilings],
|
|
257
281
|
);
|
|
282
|
+
// The fund view (ETF) has its own groups; each view keeps its own choice.
|
|
283
|
+
const groups = forms ? FUND_FORM_GROUPS : COMPANY_FORM_GROUPS;
|
|
284
|
+
const [storedGroup, setFormGroup] = usePluginPaneState<string>(forms ? "fundFormGroup" : "formGroup", ALL_FORMS);
|
|
285
|
+
const formGroup = groups.some((group) => group.value === storedGroup) ? storedGroup : ALL_FORMS;
|
|
286
|
+
// A ticker can map to more than one filer (CIK); each can be listed alone.
|
|
287
|
+
const issuers = useMemo(() => secFilingIssuers(formFilings), [formFilings]);
|
|
288
|
+
const [storedIssuer, setIssuer] = usePluginPaneState<string>("issuerCik", ALL_FORMS);
|
|
289
|
+
const issuerCik = issuers.some((issuer) => issuer.cik === storedIssuer) ? storedIssuer : ALL_FORMS;
|
|
290
|
+
const filings = useMemo(() => {
|
|
291
|
+
const grouped = filterFilingsByGroup(formFilings, groups, formGroup);
|
|
292
|
+
return issuerCik === ALL_FORMS ? grouped : grouped.filter((filing) => filing.cik === issuerCik);
|
|
293
|
+
}, [formFilings, formGroup, groups, issuerCik]);
|
|
258
294
|
const filterHistoryCapped = !!forms && issuerFilings.length >= SEC_FILING_FETCH_LIMIT;
|
|
259
295
|
const [visibleCount, setVisibleCount] = useState(SEC_FILING_PAGE_SIZE);
|
|
260
296
|
const visibleFilings = useMemo(() => filings.slice(0, visibleCount), [filings, visibleCount]);
|
|
@@ -266,7 +302,7 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
|
|
|
266
302
|
);
|
|
267
303
|
useEffect(() => {
|
|
268
304
|
setVisibleCount(SEC_FILING_PAGE_SIZE);
|
|
269
|
-
}, [ticker?.metadata.ticker, ticker?.metadata.exchange, formsSetting]);
|
|
305
|
+
}, [ticker?.metadata.ticker, ticker?.metadata.exchange, formsSetting, formGroup, issuerCik]);
|
|
270
306
|
const loading = filingsEntry?.phase === "loading" || filingsEntry?.phase === "refreshing";
|
|
271
307
|
// A restored or shared open filing can sit past the first page, or be gone
|
|
272
308
|
// from the feed entirely.
|
|
@@ -284,7 +320,7 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
|
|
|
284
320
|
const error = filingsEntry?.error && filingsEntry.error.reasonCode !== "NO_DATA" ? filingsEntry.error.message : null;
|
|
285
321
|
// Hosted, the only filings source is Gloom Cloud, which needs an account.
|
|
286
322
|
const cloudSession = useResearchCloudSession();
|
|
287
|
-
const authWall =
|
|
323
|
+
const authWall = formFilings.length === 0 && isCloudSessionRequired(error);
|
|
288
324
|
|
|
289
325
|
const openFiling = openItemId
|
|
290
326
|
? filings.find((filing) => filing.accessionNumber === openItemId) ?? null
|
|
@@ -298,14 +334,30 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
|
|
|
298
334
|
|| documentsEntry?.phase === "refreshing"
|
|
299
335
|
);
|
|
300
336
|
|
|
301
|
-
//
|
|
302
|
-
//
|
|
337
|
+
// The open filing's content comes first. Insider forms say who traded only
|
|
338
|
+
// in their text, so the ones around the cursor are read after it, one at a
|
|
339
|
+
// time, for their rows; the rest of the list is never queued, which would
|
|
340
|
+
// fire dozens of requests for rows nobody looks at. Read text is cached.
|
|
303
341
|
const selectedFiling = visibleFilings[selectedIdx];
|
|
304
|
-
|
|
342
|
+
// The rows on screen, as a share of the list so it holds for terminal rows and desktop pixels alike.
|
|
343
|
+
const [onScreen, setOnScreen] = useState({ first: 0, count: height });
|
|
344
|
+
const trackScroll = useCallback(() => {
|
|
345
|
+
loadMore();
|
|
346
|
+
const box = scrollRef.current;
|
|
347
|
+
if (!box?.viewport || !(box.scrollHeight > 0) || visibleFilings.length === 0) return;
|
|
348
|
+
const first = Math.floor((box.scrollTop / box.scrollHeight) * visibleFilings.length);
|
|
349
|
+
const count = Math.ceil((box.viewport.height / box.scrollHeight) * visibleFilings.length) + 1;
|
|
350
|
+
setOnScreen((current) => (current.first === first && current.count === count ? current : { first, count }));
|
|
351
|
+
}, [loadMore, visibleFilings.length]);
|
|
352
|
+
const nearbyOwnership = useMemo(() => [
|
|
353
|
+
...(selectedFiling ? [selectedFiling] : []),
|
|
354
|
+
...visibleFilings.slice(onScreen.first, onScreen.first + Math.min(OWNERSHIP_ROWS_READ, onScreen.count)),
|
|
355
|
+
].map(rowSummaryTarget).filter((target): target is SecFilingItem => !!target), [onScreen, selectedFiling, visibleFilings]);
|
|
356
|
+
const viewedTargets = useMemo(() => [
|
|
305
357
|
...(openFiling ? [openFiling] : []),
|
|
306
358
|
...buildInlineFilingContentTargets(openFiling, openDocuments),
|
|
307
|
-
|
|
308
|
-
], [
|
|
359
|
+
], [openDocuments, openFiling]);
|
|
360
|
+
const contentTargets = useMemo(() => [...viewedTargets, ...nearbyOwnership], [nearbyOwnership, viewedTargets]);
|
|
309
361
|
const { contentCache, contentErrors, retry: retryContent, loadingKey } = useSecFilingContentCache({
|
|
310
362
|
scopeKey: `${ticker?.metadata.ticker ?? "none"}:${ticker?.metadata.exchange ?? ""}:${eligibleTicker}`,
|
|
311
363
|
targets: contentTargets,
|
|
@@ -352,14 +404,24 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
|
|
|
352
404
|
}
|
|
353
405
|
}, [selectedIdx, setSelectedIdx, visibleFilings.length]);
|
|
354
406
|
|
|
407
|
+
// The pane's own ticker opens its description; a fund or equity both have one.
|
|
408
|
+
const issuerTicker = useMemo(() => (ticker && paneType ? {
|
|
409
|
+
symbol: ticker.metadata.ticker,
|
|
410
|
+
open: () => openDescription(ticker.metadata.ticker, ticker.metadata.exchange || undefined),
|
|
411
|
+
} : null), [openDescription, paneType, ticker]);
|
|
412
|
+
const hints = useMemo<PaneHint[]>(() => (issuerTicker
|
|
413
|
+
? [{ id: "sec-description", key: "d", label: "es", title: `Open ${issuerTicker.symbol}`, onPress: issuerTicker.open }]
|
|
414
|
+
: []), [issuerTicker]);
|
|
355
415
|
usePaneStatusLinkFooter({
|
|
356
416
|
registrationId: "sec",
|
|
357
417
|
focused,
|
|
358
418
|
url: openFiling?.filingUrl,
|
|
359
419
|
source: openFiling?.form,
|
|
360
420
|
label: "filing",
|
|
361
|
-
|
|
421
|
+
// Rows read in the background for their insiders do not hold up the pane.
|
|
422
|
+
loading: loading || loadingDocuments || (!!loadingKey && viewedTargets.some((target) => target.accessionNumber === loadingKey)),
|
|
362
423
|
error: authWall ? null : error ?? detailError,
|
|
424
|
+
hints,
|
|
363
425
|
showOpenHint: !!openFiling?.filingUrl,
|
|
364
426
|
});
|
|
365
427
|
|
|
@@ -373,25 +435,48 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
|
|
|
373
435
|
title: "Fund filings",
|
|
374
436
|
});
|
|
375
437
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
438
|
+
const queryFilters = useMemo<QueryBarFilter[]>(() => [
|
|
439
|
+
{
|
|
440
|
+
id: "form",
|
|
441
|
+
label: "Form",
|
|
442
|
+
value: formGroup,
|
|
443
|
+
defaultValue: ALL_FORMS,
|
|
444
|
+
options: [{ value: ALL_FORMS, label: forms ? "All fund forms" : "All forms" }, ...groups.map((group) => ({ value: group.value, label: group.label }))],
|
|
445
|
+
onChange: (value: string) => { setFormGroup(value); setSelectedIdx(0, { immediate: true }); setOpenItemId(null); },
|
|
446
|
+
},
|
|
447
|
+
...(issuers.length > 1 ? [{
|
|
448
|
+
id: "issuer",
|
|
449
|
+
label: "Filer",
|
|
450
|
+
value: issuerCik,
|
|
451
|
+
defaultValue: ALL_FORMS,
|
|
452
|
+
options: [{ value: ALL_FORMS, label: "All filers" }, ...issuers.map((issuer) => ({ value: issuer.cik, label: issuer.companyName || `CIK ${issuer.cik}` }))],
|
|
453
|
+
onChange: (value: string) => { setIssuer(value); setSelectedIdx(0, { immediate: true }); setOpenItemId(null); },
|
|
454
|
+
}] : []),
|
|
455
|
+
], [formGroup, forms, groups, issuerCik, issuers, setFormGroup, setIssuer, setOpenItemId, setSelectedIdx]);
|
|
456
|
+
// The ticker and the filer once, beside the filters: every row is their filing.
|
|
457
|
+
const issuerMeta = issuers.length === 1 ? secIssuerLabel(issuers[0]!).replace(/CIK 0+/, "CIK ") : undefined;
|
|
458
|
+
const queryBar = (
|
|
459
|
+
<QueryBar
|
|
460
|
+
width={width}
|
|
461
|
+
filters={queryFilters}
|
|
462
|
+
meta={issuerMeta}
|
|
463
|
+
leading={issuerTicker
|
|
464
|
+
? <TickerBadgeList symbols={[issuerTicker.symbol]} width={displayWidth(issuerTicker.symbol) + 2} listed onOpen={issuerTicker.open} />
|
|
465
|
+
: undefined}
|
|
466
|
+
/>
|
|
467
|
+
);
|
|
383
468
|
|
|
384
469
|
if (!ticker) {
|
|
385
470
|
return <EmptyState title="No ticker selected." message="Select a ticker to view SEC filings." />;
|
|
386
471
|
}
|
|
387
472
|
if (!eligibleTicker) {
|
|
388
|
-
return renderFilingNotice(forms ? "Fund filings are only shown for US-listed funds and equities." : "SEC filings are only shown for US equities."
|
|
473
|
+
return renderFilingNotice(forms ? "Fund filings are only shown for US-listed funds and equities." : "SEC filings are only shown for US equities.");
|
|
389
474
|
}
|
|
390
475
|
if (authWall) return <SignInWall placement="sec-signin" action="view SEC filings" needsVerification={cloudSession.needsVerification} />;
|
|
391
476
|
if (loading && filings.length === 0) return <Spinner label="Loading SEC filings..." />;
|
|
392
|
-
if (error &&
|
|
393
|
-
if (
|
|
394
|
-
return renderFilingNotice(forms ? `No fund filings for ${ticker.metadata.ticker}.` : `No recent SEC filings for ${ticker.metadata.ticker}
|
|
477
|
+
if (error && formFilings.length === 0) return <EmptyState title="SEC filings unavailable." message={error} />;
|
|
478
|
+
if (formFilings.length === 0) {
|
|
479
|
+
return renderFilingNotice(forms ? `No fund filings for ${ticker.metadata.ticker}.` : `No recent SEC filings for ${ticker.metadata.ticker}.`);
|
|
395
480
|
}
|
|
396
481
|
|
|
397
482
|
return (
|
|
@@ -400,6 +485,9 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
|
|
|
400
485
|
height={height}
|
|
401
486
|
focused={focused}
|
|
402
487
|
items={toFeedItems(
|
|
488
|
+
colors,
|
|
489
|
+
issuerTicker,
|
|
490
|
+
issuers.length > 1 && issuerCik === ALL_FORMS,
|
|
403
491
|
visibleFilings,
|
|
404
492
|
openFiling?.accessionNumber,
|
|
405
493
|
contentCache,
|
|
@@ -414,12 +502,12 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
|
|
|
414
502
|
openItemId={openItemId}
|
|
415
503
|
onOpenItemIdChange={setOpenItemId}
|
|
416
504
|
prefetchDetail={prefetchDocuments}
|
|
417
|
-
rootBefore={
|
|
505
|
+
rootBefore={queryBar}
|
|
418
506
|
sourceLabel="Form"
|
|
419
507
|
titleLabel="Filing"
|
|
420
|
-
emptyStateTitle="No SEC filings."
|
|
508
|
+
emptyStateTitle={formGroup === ALL_FORMS ? "No SEC filings." : "No filings of these forms."}
|
|
421
509
|
scrollRef={scrollRef}
|
|
422
|
-
onBodyScrollActivity={
|
|
510
|
+
onBodyScrollActivity={trackScroll}
|
|
423
511
|
/>
|
|
424
512
|
);
|
|
425
513
|
}
|