gloomberb 0.16.3 → 0.16.4
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 +4 -1
- package/src/api-client/askg.ts +43 -0
- package/src/api-client/auth.ts +15 -0
- package/src/api-client/beneficial-owners.ts +4 -0
- package/src/api-client/chat.ts +19 -0
- package/src/api-client/company-kpis.ts +14 -2
- package/src/api-client/data.ts +34 -16
- package/src/api-client/index.ts +12 -0
- package/src/api-client/normalizers.ts +27 -0
- package/src/api-client/paths.ts +88 -23
- package/src/api-client/request.ts +2 -1
- package/src/api-client/research-activity.ts +31 -0
- package/src/api-client/socket.ts +39 -8
- package/src/api-client/types.ts +57 -1
- package/src/api-client/upgrade-experiment.ts +90 -0
- package/src/app/global-shortcuts.ts +11 -1
- package/src/app/keybindings/actions.ts +10 -0
- package/src/app/pane-runtime/index.tsx +8 -0
- package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +65 -25
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
- package/src/app/runtime/desktop-deeplink.ts +5 -0
- package/src/app/star-prompt/model.ts +98 -0
- package/src/app/star-prompt/runtime.ts +41 -0
- package/src/app/star-prompt/status-line.tsx +190 -0
- package/src/app.tsx +13 -7
- package/src/architecture/core-builtin-allowlist.json +70 -0
- package/src/brokers/signed-in/adapter.ts +33 -2
- package/src/brokers/signed-in/client.ts +10 -0
- package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
- package/src/brokers/signed-in/sign-in.ts +62 -5
- package/src/capabilities/registry.ts +8 -1
- package/src/capabilities/types.ts +2 -0
- package/src/cli/commands/crypto-hints.ts +12 -4
- package/src/cli/commands/market.ts +359 -99
- package/src/cli/commands/options-chain.ts +172 -0
- package/src/cli/commands/options-leaps.ts +422 -0
- package/src/cli/commands/overview.ts +164 -26
- package/src/cli/commands/search.ts +29 -8
- package/src/cli/commands/system.ts +99 -19
- package/src/cli/commands/themes.ts +64 -0
- package/src/cli/commands/ticker.ts +62 -30
- package/src/cli/desktop-pane-shot.ts +97 -16
- package/src/cli/errors.ts +25 -2
- package/src/cli/freshness.ts +7 -4
- package/src/cli/fx-pair.ts +57 -0
- package/src/cli/help.ts +14 -1
- package/src/cli/index-roots.ts +51 -0
- package/src/cli/index.ts +9 -6
- package/src/cli/listing-arg.ts +211 -3
- package/src/cli/not-a-ticker.ts +37 -0
- package/src/cli/options.ts +50 -6
- package/src/cli/pane-functions/capabilities.ts +8 -2
- package/src/cli/pane-functions/catalog.ts +13 -2
- package/src/cli/pane-functions/data.ts +36 -5
- package/src/cli/pane-functions/dom.ts +21 -13
- package/src/cli/pane-functions/freshness.ts +205 -26
- package/src/cli/pane-functions/function-help.ts +67 -22
- package/src/cli/pane-functions/headless.ts +5 -4
- package/src/cli/pane-functions/index.ts +3 -0
- package/src/cli/pane-functions/market-session.ts +133 -0
- package/src/cli/pane-functions/options.ts +15 -4
- package/src/cli/pane-functions/report-notices.ts +63 -0
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +4 -6
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +105 -23
- package/src/cli/registry.ts +2 -1
- package/src/cli/result.ts +50 -13
- package/src/cli/row-window.ts +76 -0
- package/src/cli/scroll-truncation.ts +34 -0
- package/src/components/chart/composite/composite-chart.tsx +43 -4
- package/src/components/chart/composite/legend.tsx +39 -6
- package/src/components/chart/composite/rasterizer.ts +64 -4
- package/src/components/chart/composite/series-cues.ts +71 -0
- package/src/components/chart/composite/text-renderer.ts +16 -8
- package/src/components/chart/composite/types.ts +3 -0
- package/src/components/command-bar/commands/direct/index.ts +9 -1
- package/src/components/command-bar/commands/plugin/items.ts +3 -1
- package/src/components/command-bar/commands/registry.ts +7 -0
- package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/routes/root/command-items.ts +2 -0
- package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
- package/src/components/command-bar/routes/root/results.ts +40 -10
- package/src/components/command-bar/routes/root/runtime.ts +8 -0
- package/src/components/command-bar/routes/root/search-providers.ts +97 -14
- package/src/components/command-bar/surface/index.tsx +18 -4
- package/src/components/command-bar/theme-picker.tsx +48 -13
- package/src/components/command-bar/workflow/ops.ts +5 -3
- package/src/components/data-table/view.tsx +28 -9
- package/src/components/layout/floating-pane.tsx +6 -2
- package/src/components/layout/market-summary.ts +23 -9
- package/src/components/layout/pane/content.tsx +46 -29
- package/src/components/layout/pane/cycle-order.ts +2 -11
- package/src/components/layout/pane/header.tsx +2 -1
- package/src/components/layout/pane/title.ts +1 -1
- package/src/components/layout/presentation.ts +22 -0
- package/src/components/layout/shell/active-drag.ts +1 -1
- package/src/components/layout/shell/drag/index.ts +4 -0
- package/src/components/layout/shell/drag/live.ts +76 -5
- package/src/components/layout/shell/drag/runtime.ts +8 -8
- package/src/components/layout/shell/index.tsx +47 -6
- package/src/components/layout/shell/layout-state.ts +5 -11
- package/src/components/layout/shell/menu.ts +28 -1
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +51 -10
- package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
- package/src/components/layout/shell/pane/new-layout.ts +116 -0
- package/src/components/layout/shell/shortcuts.ts +5 -2
- package/src/components/layout/shell/visible-layout.ts +44 -1
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-text.tsx +9 -5
- package/src/components/metric-treemap/heat-surface.tsx +27 -8
- package/src/components/metric-treemap/heat-tiles.ts +48 -17
- package/src/components/onboarding/onboarding-frame.tsx +33 -10
- package/src/components/onboarding/onboarding-wizard.tsx +49 -40
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
- package/src/components/onboarding/pro-step-layout.ts +86 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
- package/src/components/ui/button.tsx +10 -0
- package/src/components/ui/data-table/index.tsx +38 -0
- package/src/components/ui/data-table/types.ts +8 -1
- package/src/components/ui/data-table/visible-range.ts +2 -0
- package/src/components/ui/icon.tsx +11 -2
- package/src/components/ui/index.ts +1 -1
- package/src/core/connection-health.ts +1 -1
- package/src/core/state/app/layout-reducer.ts +89 -3
- package/src/core/state/app/state.ts +8 -5
- package/src/core/state/app/types.ts +13 -2
- package/src/data/config/layout.ts +55 -21
- package/src/data/config/store/fold-panes.ts +149 -0
- package/src/data/config/store/migrations.ts +7 -0
- package/src/data/config/store/node.ts +27 -6
- package/src/data/config/store/normalize.ts +37 -3
- package/src/i18n/es.ts +14 -1
- package/src/i18n/ja.ts +14 -1
- package/src/i18n/ko.ts +14 -1
- package/src/i18n/zh-cn.ts +14 -1
- package/src/i18n/zh-tw.ts +14 -1
- package/src/layout/desks.ts +1 -2
- package/src/layout/pane-layout-move.ts +264 -0
- package/src/layout/pane-manager/hidden-panes.ts +160 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/entries.ts +7 -1
- package/src/market-data/coordinator/quotes.ts +2 -2
- package/src/market-data/market/freshness.ts +118 -18
- package/src/market-data/market/status.ts +40 -0
- package/src/market-data/options-alternatives.ts +69 -0
- package/src/market-data/quotes/contributions.ts +19 -2
- package/src/market-data/quotes/metadata.ts +16 -0
- package/src/market-data/quotes/screener-live-quotes.ts +56 -5
- package/src/plugins/builtin/account-management/keyboard.ts +3 -0
- package/src/plugins/builtin/account-management/model.ts +1 -0
- package/src/plugins/builtin/account-management/navigation.ts +1 -1
- package/src/plugins/builtin/account-management/pane.tsx +31 -4
- package/src/plugins/builtin/alerts/quotes.ts +3 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
- package/src/plugins/builtin/analytics/risk-model.ts +37 -8
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
- package/src/plugins/builtin/backtest/pane.tsx +1 -1
- package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/broker-manager/model.ts +8 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
- package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
- package/src/plugins/builtin/broker-manager/table.ts +5 -0
- package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
- package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
- package/src/plugins/builtin/chat/channels.ts +34 -36
- package/src/plugins/builtin/chat/command-bar.ts +138 -0
- package/src/plugins/builtin/chat/composer-commands.ts +21 -1
- package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
- package/src/plugins/builtin/chat/content/footer.tsx +5 -3
- package/src/plugins/builtin/chat/content/index.tsx +21 -1
- package/src/plugins/builtin/chat/content/mentions.ts +2 -1
- package/src/plugins/builtin/chat/content/user-map.ts +3 -1
- package/src/plugins/builtin/chat/controller/index.ts +48 -0
- package/src/plugins/builtin/chat/conversations.ts +256 -0
- package/src/plugins/builtin/chat/discord-command.ts +91 -0
- package/src/plugins/builtin/chat/edit-window.ts +12 -6
- package/src/plugins/builtin/chat/ghost-user.ts +14 -0
- package/src/plugins/builtin/chat/index.tsx +40 -2
- package/src/plugins/builtin/chat/layout.ts +2 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
- package/src/plugins/builtin/chat/message/header.tsx +24 -15
- package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
- package/src/plugins/builtin/chat/message/render-state.ts +3 -0
- package/src/plugins/builtin/chat/pane-requests.ts +60 -0
- package/src/plugins/builtin/chat/pane-state.ts +32 -2
- package/src/plugins/builtin/chat/pane.tsx +8 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
- package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
- package/src/plugins/builtin/cloud/askg/model.ts +129 -70
- package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
- package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
- package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
- package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
- package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
- package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
- package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
- package/src/plugins/builtin/cloud/plugin.tsx +36 -1
- package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
- package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
- package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
- package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
- package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
- package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
- package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
- package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
- package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
- package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
- package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
- package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
- package/src/plugins/builtin/comment-letters/client.ts +1 -3
- package/src/plugins/builtin/company-kpis/client.ts +23 -8
- package/src/plugins/builtin/company-kpis/headless.ts +3 -1
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
- package/src/plugins/builtin/composite-plugins.ts +107 -64
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
- package/src/plugins/builtin/credit-boards/index.tsx +21 -21
- package/src/plugins/builtin/credit-boards/model.ts +0 -1
- package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
- package/src/plugins/builtin/debt-maturities/client.ts +37 -9
- package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/data.ts +28 -7
- package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
- package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
- package/src/plugins/builtin/econ/calendar-model.ts +42 -17
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/index.tsx +68 -22
- package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +17 -0
- package/src/plugins/builtin/executives/data.ts +17 -6
- package/src/plugins/builtin/executives/pane.tsx +16 -7
- package/src/plugins/builtin/filing-events/pane.tsx +22 -5
- package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
- package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
- package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
- package/src/plugins/builtin/gpu/headless.ts +9 -3
- package/src/plugins/builtin/gpu/model.ts +14 -5
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
- package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
- package/src/plugins/builtin/holders/headless.ts +6 -1
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/insider/index.tsx +4 -4
- package/src/plugins/builtin/iv-history/charts.tsx +21 -7
- package/src/plugins/builtin/iv-history/client.ts +12 -2
- package/src/plugins/builtin/iv-history/format.ts +33 -2
- package/src/plugins/builtin/iv-history/headless.ts +22 -10
- package/src/plugins/builtin/iv-history/index.tsx +3 -3
- package/src/plugins/builtin/iv-history/model.ts +61 -13
- package/src/plugins/builtin/iv-history/pane.tsx +11 -3
- package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
- package/src/plugins/builtin/jobs/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
- package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +109 -39
- package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
- package/src/plugins/builtin/layout-manager/index.tsx +9 -1
- package/src/plugins/builtin/macro-day/client.ts +24 -0
- package/src/plugins/builtin/macro-day/headless.ts +11 -8
- package/src/plugins/builtin/macro-day/index.tsx +3 -0
- package/src/plugins/builtin/macro-day/model.ts +11 -6
- package/src/plugins/builtin/macro-day/pane.tsx +12 -8
- package/src/plugins/builtin/macro-day/release-table.ts +40 -0
- package/src/plugins/builtin/macro-day/releases.ts +62 -44
- package/src/plugins/builtin/market-heatmap/data.ts +25 -5
- package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
- package/src/plugins/builtin/market-heatmap/model.ts +94 -3
- package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
- package/src/plugins/builtin/market-movers/headless.ts +31 -4
- package/src/plugins/builtin/market-movers/index.tsx +8 -1
- package/src/plugins/builtin/market-movers/model.ts +71 -17
- package/src/plugins/builtin/market-movers/table.ts +50 -23
- package/src/plugins/builtin/news/headless.ts +9 -2
- package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
- package/src/plugins/builtin/openfda/client.ts +1 -1
- package/src/plugins/builtin/options/carry.ts +95 -0
- package/src/plugins/builtin/options/index.tsx +58 -5
- package/src/plugins/builtin/options/strike-window.ts +183 -0
- package/src/plugins/builtin/options/table.ts +55 -3
- package/src/plugins/builtin/options/types.ts +8 -1
- package/src/plugins/builtin/options/view.tsx +161 -12
- package/src/plugins/builtin/options-positioning/headless.ts +4 -2
- package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +73 -17
- package/src/plugins/builtin/options-scenario/headless.ts +71 -24
- package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
- package/src/plugins/builtin/options-scenario/index.tsx +9 -2
- package/src/plugins/builtin/options-scenario/model.ts +33 -0
- package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
- package/src/plugins/builtin/pe-band/model.ts +2 -2
- package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
- package/src/plugins/builtin/perps/equity-row.tsx +8 -5
- package/src/plugins/builtin/perps/model.ts +16 -5
- package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
- package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
- package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
- package/src/plugins/builtin/plugin-module.ts +87 -4
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
- package/src/plugins/builtin/portfolio-list/cli/render.ts +87 -18
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
- package/src/plugins/builtin/portfolio-list/headless.ts +38 -7
- package/src/plugins/builtin/realized-vol/headless.ts +3 -0
- package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
- package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
- package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
- package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
- package/src/plugins/builtin/risk-factors/data.ts +23 -9
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
- package/src/plugins/builtin/seasonality/pane.tsx +1 -1
- package/src/plugins/builtin/sec/index.tsx +5 -5
- package/src/plugins/builtin/sectors/client.ts +10 -2
- package/src/plugins/builtin/sectors/headless.ts +35 -6
- package/src/plugins/builtin/sectors/index.tsx +18 -1
- package/src/plugins/builtin/sectors/sector-model.ts +150 -41
- package/src/plugins/builtin/shared/command-tab.ts +51 -0
- package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
- package/src/plugins/builtin/shared/members-menu.tsx +4 -4
- package/src/plugins/builtin/shared/report-freshness.ts +50 -2
- package/src/plugins/builtin/shared/ticker-request.ts +25 -1
- package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
- package/src/plugins/builtin/shared/volatility/options.ts +1 -46
- package/src/plugins/builtin/short-interest/index.tsx +19 -4
- package/src/plugins/builtin/short-interest/surface.tsx +4 -4
- package/src/plugins/builtin/short-volume/index.tsx +1 -10
- package/src/plugins/builtin/supply-chain/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/headless.ts +43 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
- package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
- package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
- package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
- package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
- package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
- package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
- package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
- package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
- package/src/plugins/builtin/vol-surface/client.ts +6 -2
- package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
- package/src/plugins/builtin/vol-surface/headless.ts +124 -20
- package/src/plugins/builtin/vol-surface/model.ts +23 -11
- package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
- package/src/plugins/builtin/vol-surface/tables.ts +124 -0
- package/src/plugins/builtin/world-indices/client.ts +22 -4
- package/src/plugins/builtin/world-indices/headless.ts +54 -27
- package/src/plugins/builtin/world-indices/model.ts +4 -2
- package/src/plugins/builtin/world-indices/table.tsx +13 -2
- package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
- package/src/plugins/builtin/world-venue-map/index.ts +1 -1
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/builtin-editorial.ts +126 -0
- package/src/plugins/catalog-backend.ts +16 -9
- package/src/plugins/catalog-browser.ts +127 -63
- package/src/plugins/catalog-manifest.ts +73 -40
- package/src/plugins/catalog-ui.ts +26 -6
- package/src/plugins/extracted-plugins.ts +7 -1
- package/src/plugins/ownership.ts +150 -7
- package/src/plugins/plugin-functions.ts +54 -0
- package/src/plugins/plugin-off.ts +36 -0
- package/src/plugins/registry/host-actions.ts +16 -0
- package/src/plugins/registry/index.ts +84 -24
- package/src/plugins/registry/plugin-state.ts +25 -1
- package/src/plugins/runtime/context.tsx +29 -0
- package/src/react/input.ts +48 -1
- package/src/remote/app-host.tsx +5 -0
- package/src/remote/controller.ts +3 -53
- package/src/remote/matching.ts +69 -0
- package/src/remote/schema.ts +1 -1
- package/src/remote/semantic-tree.tsx +14 -0
- package/src/remote/types.ts +2 -0
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/query-bar.tsx +59 -32
- package/src/renderers/dom/host/box.tsx +1 -1
- package/src/renderers/dom/host/drag-shield.ts +17 -6
- package/src/renderers/dom/host/live-frame.ts +10 -2
- package/src/renderers/dom/input-host.tsx +6 -1
- package/src/renderers/dom/styles.css +13 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +5 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
- package/src/renderers/electrobun/view/main.tsx +4 -1
- package/src/renderers/opentui/input-host.tsx +6 -1
- package/src/renderers/opentui/start.tsx +9 -0
- package/src/shares/portable-layout.ts +16 -6
- package/src/sources/gloomberb-cloud/index.ts +19 -10
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -1
- package/src/sources/listing-symbols.ts +11 -5
- package/src/sources/provider-errors.ts +88 -3
- package/src/sources/provider-router/cached-routes.ts +8 -4
- package/src/sources/provider-router/financial-routes.ts +11 -5
- package/src/sources/provider-router/financials.ts +42 -15
- package/src/sources/provider-router/history.ts +27 -11
- package/src/sources/provider-router/index.ts +1 -1
- package/src/sources/provider-router/primary.ts +23 -5
- package/src/sources/sec-edgar.ts +68 -8
- package/src/sync/core-contributors.ts +4 -3
- package/src/test-support/cli-context.ts +2 -1
- package/src/test-support/data-provider.ts +34 -0
- package/src/test-support/shell-pane-keys.tsx +1 -0
- package/src/theme/series-colors.ts +46 -0
- package/src/theme/themes.ts +149 -0
- package/src/tickers/search/index.ts +22 -2
- package/src/tickers/search/ranking.ts +45 -1
- package/src/tickers/search/types.ts +3 -1
- package/src/time-series/resolve.ts +1 -14
- package/src/types/config.ts +61 -1
- package/src/types/headless.ts +11 -0
- package/src/types/instrument.ts +8 -0
- package/src/types/plugin.ts +49 -3
- package/src/ui/host.tsx +8 -4
- package/src/ui/index.tsx +2 -0
- package/src/utils/cli-output.ts +14 -1
- package/src/utils/exchanges.ts +13 -0
- package/src/utils/format.ts +2 -1
- package/src/utils/fx-market-hours.ts +34 -0
- package/src/utils/option-expiry.ts +126 -0
- package/src/utils/price-history.ts +39 -6
- package/src/utils/rpc-timeout-error.ts +6 -2
- package/src/utils/sec-eps-basis.ts +159 -30
- package/src/utils/sec.ts +38 -34
- package/src/utils/throttled-fetch.ts +51 -22
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PricePoint, TickerFinancials } from "../types/financials";
|
|
2
2
|
import { canonicalExchange, resolveExchangeTimeZone } from "./exchanges";
|
|
3
3
|
import { isRoundTheClockCoin } from "./crypto-pair";
|
|
4
|
+
import { isCurrencyPairListing, latestFxWeekClose } from "./fx-market-hours";
|
|
4
5
|
import { hasPublishedSessionCalendar, isRegularSessionTime, isTimestampStaleForExchangeSession, latestRegularSessionClose, latestRegularSessionOpen, sessionCalendarTimeZone } from "../market-data/market/freshness";
|
|
5
6
|
import { zonedDateTimeParts } from "./zoned-date-time";
|
|
6
7
|
import { regularHistorySessionStaleness } from "../market-data/history-session";
|
|
@@ -29,6 +30,16 @@ interface PriceHistoryFreshnessOptions {
|
|
|
29
30
|
session?: HistorySession;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
/**
|
|
34
|
+
* How old the newest bar of a current copy may be. Completed bars are
|
|
35
|
+
* timestamped at their opening time, so before the next one is delivered the
|
|
36
|
+
* newest may be almost two intervals plus the feed delay old; finer bars keep
|
|
37
|
+
* a floor.
|
|
38
|
+
*/
|
|
39
|
+
function allowedBarLagMs(intervalMs: number | null): number {
|
|
40
|
+
return Math.max(MAX_SAME_SESSION_HISTORY_LAG_MS, intervalMs != null ? 2 * intervalMs + DELAYED_HISTORY_ALLOWANCE_MS : 0);
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
export function priceHistoryIntervalMs(interval: string): number | null {
|
|
33
44
|
const parsed = parseBarInterval(interval);
|
|
34
45
|
return parsed ? parsed.count * BAR_INTERVAL_UNIT_MS[parsed.unit] : null;
|
|
@@ -204,11 +215,7 @@ export function isPriceHistoryStaleForCurrentWindow(
|
|
|
204
215
|
if (regularStale !== null) return regularStale;
|
|
205
216
|
}
|
|
206
217
|
const age = now - latestTime;
|
|
207
|
-
|
|
208
|
-
// completed bar is delivered, the newest bar may be almost two intervals
|
|
209
|
-
// plus the feed delay old. Keep the existing tolerance for finer bars.
|
|
210
|
-
const allowedLag = Math.max(MAX_SAME_SESSION_HISTORY_LAG_MS,
|
|
211
|
-
intervalMs != null ? 2 * intervalMs + DELAYED_HISTORY_ALLOWANCE_MS : 0);
|
|
218
|
+
const allowedLag = allowedBarLagMs(intervalMs);
|
|
212
219
|
if (age <= allowedLag) return false;
|
|
213
220
|
const exchange = options.exchange || "NASDAQ";
|
|
214
221
|
// Bars that reach the close of the venue's latest session stay current
|
|
@@ -310,6 +317,31 @@ function isBarBeforeSession(latestTime: number, session: string, intervalMs: num
|
|
|
310
317
|
return dayNumber(session) >= dayNumber(bar) + Math.max(1, Math.round(intervalMs / DAY_MS));
|
|
311
318
|
}
|
|
312
319
|
|
|
320
|
+
/**
|
|
321
|
+
* An intraday copy fetched before the venue's latest session settled holds
|
|
322
|
+
* that session only as far as the fetch. Once the session is over, or the
|
|
323
|
+
* next has begun, a copy whose last bar stops short of the close by more than
|
|
324
|
+
* a current copy's bars may lag is refetched. A copy fetched after the
|
|
325
|
+
* session settled holds all of it, however early its last bar: a thin listing
|
|
326
|
+
* need not trade late. A currency pair's session is the FX week, which closes
|
|
327
|
+
* Friday 17:00 New York. Coins never close, and within the FX week neither do
|
|
328
|
+
* pairs: isPriceHistoryStaleForCurrentWindow holds those copies to the present.
|
|
329
|
+
*/
|
|
330
|
+
function intradayHistoryFetchState(
|
|
331
|
+
points: PricePoint[], fetchedAt: number, now: number, intervalMs: number, options: CalendarHistoryFetchOptions,
|
|
332
|
+
): CalendarHistoryFetchState {
|
|
333
|
+
if (isRoundTheClockCoin(options.symbol, options.exchange)) return "current";
|
|
334
|
+
const settledBy = now - SESSION_BAR_SETTLE_MS;
|
|
335
|
+
// Bare symbols resolve to their US listing at the sources.
|
|
336
|
+
const close = isCurrencyPairListing(options.symbol, options.exchange) ? latestFxWeekClose(settledBy)
|
|
337
|
+
: latestRegularSessionClose(canonicalExchange(options.exchange) || "NYSE", settledBy)?.close ?? null;
|
|
338
|
+
if (close === null || fetchedAt >= close + SESSION_BAR_SETTLE_MS) return "current";
|
|
339
|
+
const latest = points.findLast(hasFiniteClose);
|
|
340
|
+
const latestTime = latest ? getPricePointTimestamp(latest) : Number.NaN;
|
|
341
|
+
if (!Number.isFinite(latestTime) || latestTime >= close - allowedBarLagMs(intervalMs)) return "current";
|
|
342
|
+
return unsettledState(close + SESSION_BAR_SETTLE_MS, options.checkedAt, now);
|
|
343
|
+
}
|
|
344
|
+
|
|
313
345
|
/**
|
|
314
346
|
* A daily or coarser series changes at every close. A copy fetched before the
|
|
315
347
|
* latest settled close is outdated whatever its cache TTL. A copy fetched
|
|
@@ -327,7 +359,8 @@ export function calendarHistoryFetchState(
|
|
|
327
359
|
if (!Number.isFinite(fetchedAt) || !Number.isFinite(now) || fetchedAt >= now) return "current";
|
|
328
360
|
const normalized = normalizePriceHistory(points);
|
|
329
361
|
const intervalMs = options.intervalMs ?? inferredHistoryIntervalMs(normalized);
|
|
330
|
-
if (intervalMs == null
|
|
362
|
+
if (intervalMs == null) return "current";
|
|
363
|
+
if (intervalMs < DAY_MS) return intradayHistoryFetchState(normalized, fetchedAt, now, intervalMs, options);
|
|
331
364
|
const exchange = canonicalExchange(options.exchange);
|
|
332
365
|
if (isRoundTheClockCoin(options.symbol, options.exchange)) {
|
|
333
366
|
return now - fetchedAt > CRYPTO_BAR_MAX_AGE_MS
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* A timeout this many times past its limit means the page was suspended, not
|
|
3
|
+
* that the Bun process stalled. A page that is running fires the timer within
|
|
4
|
+
* a second or two of its limit, and a hidden one within a minute.
|
|
5
|
+
*/
|
|
6
|
+
const SLEEP_TIMEOUT_FACTOR = 2;
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
9
|
* A call to the desktop's Bun process that did not complete in time: the
|
|
@@ -23,6 +23,8 @@ export interface SecEpsBasis {
|
|
|
23
23
|
type Fact = { start?: string; end?: string; val?: number; accn?: string; filed?: string; form?: string };
|
|
24
24
|
type Row = Fact & { start: string; end: string; val: number; accn: string; filed: string };
|
|
25
25
|
type Edge = { node: string; factor: number; evidence?: SecEpsSplitEvidence };
|
|
26
|
+
type Proof = { split: Fact; before: Row; after: Row; evidence: SecEpsSplitEvidence };
|
|
27
|
+
type SplitEvent = { ratio: number; proofs: Proof[]; consistent: boolean; evidence: SecEpsSplitEvidence; node: string };
|
|
26
28
|
const date = (value: unknown): value is string => typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value)
|
|
27
29
|
&& Number.isFinite(Date.parse(`${value}T00:00:00Z`)) && new Date(`${value}T00:00:00Z`).toISOString().slice(0, 10) === value;
|
|
28
30
|
const record = (value: unknown): Record<string, unknown> => value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
@@ -47,27 +49,83 @@ function uniqueObservations(rows: Fact[]): Map<string, Row> {
|
|
|
47
49
|
return result;
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
/** When the last restated filing and the first restating one were filed: the split falls between. */
|
|
53
|
+
function cut(proofs: Proof[]): { lastRestated: string; firstRestating: string } {
|
|
54
|
+
return {
|
|
55
|
+
lastRestated: proofs.reduce((max, proof) => proof.before.filed > max ? proof.before.filed : max, ""),
|
|
56
|
+
firstRestating: proofs.reduce((min, proof) => proof.after.filed < min ? proof.after.filed : min, "9999"),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const separable = (proofs: Proof[]) => cut(proofs).lastRestated < cut(proofs).firstRestating;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* One split, however many filings repeat it and under whatever context dates
|
|
63
|
+
* (an approval date, the effective date, each period of the equity statement).
|
|
64
|
+
* Rows with one ratio and one context date are one disclosure. A split divides
|
|
65
|
+
* the filings once, so disclosures with one ratio are one split when a single
|
|
66
|
+
* filing date still separates every filing they restate from every filing that
|
|
67
|
+
* restates; two splits with one ratio leave no such date (the later split
|
|
68
|
+
* restates filings that already carry the earlier one).
|
|
69
|
+
*/
|
|
70
|
+
function splitEvents(proofs: Proof[]): SplitEvent[] {
|
|
71
|
+
const disclosures = new Map<string, Proof[]>();
|
|
72
|
+
for (const proof of proofs) {
|
|
73
|
+
const key = `${proof.split.val}:${proof.split.end}`;
|
|
74
|
+
disclosures.set(key, [...disclosures.get(key) ?? [], proof]);
|
|
75
|
+
}
|
|
76
|
+
const groups: Proof[][] = [];
|
|
77
|
+
for (const ratio of new Set(proofs.map((proof) => proof.split.val!))) {
|
|
78
|
+
const sorted = [...disclosures.values()].filter((group) => group[0]!.split.val === ratio)
|
|
79
|
+
.sort((a, b) => cut(a).firstRestating.localeCompare(cut(b).firstRestating) || cut(a).lastRestated.localeCompare(cut(b).lastRestated));
|
|
80
|
+
let current: Proof[] = [];
|
|
81
|
+
for (const disclosure of sorted) {
|
|
82
|
+
// A disclosure whose own filings admit no such date proves nothing.
|
|
83
|
+
if (!separable(disclosure)) groups.push(disclosure);
|
|
84
|
+
else if (current.length && separable([...current, ...disclosure])) current.push(...disclosure);
|
|
85
|
+
else {
|
|
86
|
+
current = [...disclosure];
|
|
87
|
+
groups.push(current);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return groups.map((group, index) => {
|
|
92
|
+
const first = [...group].sort((a, b) => a.after.filed.localeCompare(b.after.filed) || a.after.accn.localeCompare(b.after.accn)
|
|
93
|
+
|| a.split.end!.localeCompare(b.split.end!) || a.before.filed.localeCompare(b.before.filed) || a.before.end.localeCompare(b.before.end))[0]!;
|
|
94
|
+
return { ratio: group[0]!.split.val!, proofs: group, consistent: separable(group), evidence: first.evidence, node: `split:${index}` };
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
50
98
|
/**
|
|
51
99
|
* Share bases belong to source accessions, not filing dates. A split edge needs
|
|
52
100
|
* an explicit SEC split fact and unchanged same-period income with inverse EPS
|
|
53
|
-
* restatement. Repeated EPS AND diluted shares connect otherwise equal bases
|
|
101
|
+
* restatement. Repeated EPS AND diluted shares connect otherwise equal bases;
|
|
102
|
+
* where a filing tags no diluted shares, repeated EPS AND income do, if no
|
|
103
|
+
* disclosed split could leave that EPS unchanged at cent rounding.
|
|
54
104
|
* No price jump, ticker exception, or chronological assumption establishes one.
|
|
105
|
+
*
|
|
106
|
+
* Every filing that states a split and restates an earlier filing by its ratio
|
|
107
|
+
* is evidence of that split. The filings it restated share one basis and the
|
|
108
|
+
* restating filings share the next, so a split filed in several filings under
|
|
109
|
+
* several context dates is one factor, not a run of unproved disclosures.
|
|
55
110
|
*/
|
|
56
111
|
export function createSecEpsBasisResolver(payload: unknown): (fact: Fact) => { value?: number; basis?: SecEpsBasis; availableAt?: string } {
|
|
57
112
|
const splits = entries(payload, "StockholdersEquityNoteStockSplitConversionRatio1", "pure")
|
|
58
113
|
.filter((row) => row.val! > 0 && row.val !== 1)
|
|
59
114
|
.sort((a, b) => a.filed!.localeCompare(b.filed!));
|
|
60
115
|
if (splits.length === 0) return (fact) => ({ value: fact.val, availableAt: fact.filed });
|
|
61
|
-
|
|
62
|
-
const
|
|
116
|
+
// A filing that gives one split context two ratios contradicts itself.
|
|
117
|
+
const stated = new Map<string, number>();
|
|
118
|
+
const contradicted = new Set<string>();
|
|
63
119
|
for (const split of splits) {
|
|
64
|
-
|
|
65
|
-
if (
|
|
120
|
+
const context = `${split.accn}:${split.end}`;
|
|
121
|
+
if (stated.has(context) && stated.get(context) !== split.val) contradicted.add(context);
|
|
122
|
+
stated.set(context, split.val!);
|
|
66
123
|
}
|
|
67
|
-
const latest = [...
|
|
124
|
+
const latest = [...splits].sort((a, b) => b.end!.localeCompare(a.end!))[0]!;
|
|
68
125
|
const eps = uniqueObservations(entries(payload, "EarningsPerShareDiluted", "USD/shares"));
|
|
69
126
|
const shares = uniqueObservations(entries(payload, "WeightedAverageNumberOfDilutedSharesOutstanding", "shares"));
|
|
70
127
|
const income = uniqueObservations(entries(payload, "NetIncomeLoss", "USD"));
|
|
128
|
+
const key = (row: Fact) => `${row.accn}:${row.start}:${row.end}`;
|
|
71
129
|
const graph = new Map<string, Edge[]>();
|
|
72
130
|
const connect = (before: string, after: string, ratio: number, evidence?: SecEpsSplitEvidence) => {
|
|
73
131
|
graph.set(after, [...graph.get(after) ?? [], { node: before, factor: ratio, evidence }]);
|
|
@@ -75,44 +133,116 @@ export function createSecEpsBasisResolver(payload: unknown): (fact: Fact) => { v
|
|
|
75
133
|
};
|
|
76
134
|
const identical = new Map<string, Row>();
|
|
77
135
|
const periods = new Map<string, Row[]>();
|
|
78
|
-
for (const
|
|
136
|
+
for (const row of eps.values()) {
|
|
79
137
|
const period = `${row.start}:${row.end}`;
|
|
80
138
|
periods.set(period, [...periods.get(period) ?? [], row]);
|
|
81
|
-
const denominator = shares.get(key)?.val;
|
|
139
|
+
const denominator = shares.get(key(row))?.val;
|
|
82
140
|
if (!row.val || !denominator || denominator <= 0) continue;
|
|
83
141
|
const identity = `${period}:${row.val}:${denominator}`;
|
|
84
142
|
const prior = identical.get(identity);
|
|
85
143
|
if (prior && prior.accn !== row.accn) connect(prior.accn, row.accn, 1);
|
|
86
144
|
else identical.set(identity, row);
|
|
87
145
|
}
|
|
88
|
-
|
|
89
|
-
for
|
|
90
|
-
|
|
146
|
+
// Not every filer tags diluted shares (Alphabet before 2024). Where a filing
|
|
147
|
+
// tags none for the period, a repeated EPS and net income connect instead,
|
|
148
|
+
// when no disclosed split ratio could leave that EPS unchanged at cent
|
|
149
|
+
// rounding. Diluted shares that differ still keep the bases apart.
|
|
150
|
+
const ratios = [...new Set(splits.map((split) => split.val!))];
|
|
151
|
+
const telling = (value: number) => ratios.every((ratio) => Math.abs(value - value / ratio) > .005 + .005 / ratio
|
|
152
|
+
&& Math.abs(value - value * ratio) > .005 + .005 * ratio);
|
|
153
|
+
const tagged = new Set(entries(payload, "WeightedAverageNumberOfDilutedSharesOutstanding", "shares").map(key));
|
|
154
|
+
for (const rows of periods.values()) {
|
|
155
|
+
for (let i = 0; i < rows.length; i++) {
|
|
156
|
+
for (let j = i + 1; j < rows.length; j++) {
|
|
157
|
+
const [a, b] = [rows[i]!, rows[j]!];
|
|
158
|
+
if (a.accn === b.accn || a.val !== b.val || !a.val || !telling(a.val)) continue;
|
|
159
|
+
if (tagged.has(key(a)) && tagged.has(key(b))) continue;
|
|
160
|
+
const repeated = income.get(key(a))?.val;
|
|
161
|
+
if (repeated !== undefined && repeated !== 0 && repeated === income.get(key(b))?.val) connect(a.accn, b.accn, 1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// A later filing restates an earlier one's EPS for the same period by the
|
|
166
|
+
// ratio, with income unchanged.
|
|
167
|
+
const restates = (before: Row, after: Row, ratio: number) => {
|
|
168
|
+
if (before.accn === after.accn || before.filed >= after.filed || before.val === after.val || before.val * after.val <= 0) return false;
|
|
169
|
+
const beforeIncome = income.get(key(before))?.val;
|
|
170
|
+
const afterIncome = income.get(key(after))?.val;
|
|
171
|
+
if (beforeIncome === undefined || beforeIncome === 0 || beforeIncome !== afterIncome) return false;
|
|
172
|
+
// EPS reports rounded to cents have overlapping rounding intervals.
|
|
173
|
+
// This accommodates 11.91 / 4 -> 2.98 without inventing precision.
|
|
174
|
+
const tolerance = .005 + .005 / ratio;
|
|
175
|
+
return Math.abs(before.val / ratio - after.val) <= tolerance + 1e-12 && Math.abs(before.val - after.val) > tolerance * 2;
|
|
176
|
+
};
|
|
177
|
+
const proofs: Proof[] = [];
|
|
178
|
+
for (const split of splits) {
|
|
179
|
+
if (contradicted.has(`${split.accn}:${split.end}`)) continue;
|
|
91
180
|
for (const after of eps.values()) {
|
|
92
181
|
if (after.accn !== split.accn || after.filed !== split.filed || after.end >= split.end! || after.val === 0) continue;
|
|
93
182
|
for (const before of periods.get(`${after.start}:${after.end}`) ?? []) {
|
|
94
|
-
if (before
|
|
95
|
-
|
|
96
|
-
const afterIncome = income.get(`${after.accn}:${after.start}:${after.end}`)?.val;
|
|
97
|
-
if (beforeIncome === undefined || beforeIncome === 0 || beforeIncome !== afterIncome) continue;
|
|
98
|
-
// EPS reports rounded to cents have overlapping rounding intervals.
|
|
99
|
-
// This accommodates 11.91 / 4 -> 2.98 without inventing precision.
|
|
100
|
-
const tolerance = .005 + .005 / split.val!;
|
|
101
|
-
if (Math.abs(before.val / split.val! - after.val) > tolerance + 1e-12
|
|
102
|
-
|| Math.abs(before.val - after.val) <= tolerance * 2) continue;
|
|
103
|
-
const evidence: SecEpsSplitEvidence = {
|
|
183
|
+
if (!restates(before, after, split.val!)) continue;
|
|
184
|
+
proofs.push({ split, before, after, evidence: {
|
|
104
185
|
date: split.end!, ratio: split.val!, accessionNumber: split.accn!, filed: split.filed!,
|
|
105
186
|
comparativePeriodEnd: before.end, beforeAccessionNumber: before.accn, beforeEps: before.val, afterEps: after.val,
|
|
106
|
-
};
|
|
107
|
-
connect(before.accn, after.accn, split.val!, evidence);
|
|
108
|
-
proved.set(split.end!, evidence);
|
|
187
|
+
} });
|
|
109
188
|
}
|
|
110
189
|
}
|
|
111
190
|
}
|
|
191
|
+
const events = splitEvents(proofs);
|
|
192
|
+
const consistent = events.filter((event) => event.consistent);
|
|
193
|
+
// The filings a split restated share one basis and its restating filings the
|
|
194
|
+
// next. Evidence that mixes splits only connects the two filings it compares.
|
|
195
|
+
const eventOf = new Map<Fact, SplitEvent>();
|
|
196
|
+
for (const event of events) {
|
|
197
|
+
if (!event.consistent) {
|
|
198
|
+
for (const proof of event.proofs) connect(proof.before.accn, proof.after.accn, event.ratio, proof.evidence);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
connect(`${event.node}:before`, `${event.node}:after`, event.ratio, event.evidence);
|
|
202
|
+
for (const proof of event.proofs) {
|
|
203
|
+
connect(proof.before.accn, `${event.node}:before`, 1);
|
|
204
|
+
connect(`${event.node}:after`, proof.after.accn, 1);
|
|
205
|
+
eventOf.set(proof.split, event);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// A filing that repeats a proved split's restatement without stating its
|
|
209
|
+
// ratio (a 10-K after the split's 10-Qs) restates on that split when exactly
|
|
210
|
+
// one split fits: the restated filing before the split's first restating
|
|
211
|
+
// filing, the restating one after its last restated filing, and a period
|
|
212
|
+
// before the split's context dates.
|
|
213
|
+
const known = new Set([...proofs.flatMap((proof) => [proof.before.accn, proof.after.accn]),
|
|
214
|
+
...splits.filter((split) => contradicted.has(`${split.accn}:${split.end}`)).map((split) => split.accn!)]);
|
|
215
|
+
for (const after of eps.values()) {
|
|
216
|
+
if (known.has(after.accn) || after.val === 0) continue;
|
|
217
|
+
for (const before of periods.get(`${after.start}:${after.end}`) ?? []) {
|
|
218
|
+
const fits = consistent.filter((event) => restates(before, after, event.ratio)
|
|
219
|
+
&& before.filed < cut(event.proofs).firstRestating && after.filed > cut(event.proofs).lastRestated
|
|
220
|
+
&& event.proofs.some((proof) => after.end < proof.split.end!));
|
|
221
|
+
if (fits.length !== 1) continue;
|
|
222
|
+
connect(before.accn, `${fits[0]!.node}:before`, 1);
|
|
223
|
+
connect(`${fits[0]!.node}:after`, after.accn, 1);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// A row that proves nothing itself repeats a proved split: the same ratio at
|
|
227
|
+
// the context date of a row that proves it, or else stated in one of that
|
|
228
|
+
// split's filings. Only an unambiguous match counts.
|
|
229
|
+
const proving = new Set(proofs.map((proof) => proof.split));
|
|
230
|
+
for (const split of splits) {
|
|
231
|
+
if (proving.has(split) || contradicted.has(`${split.accn}:${split.end}`)) continue;
|
|
232
|
+
const candidates = consistent.filter((event) => event.ratio === split.val);
|
|
233
|
+
const dated = candidates.filter((event) => event.proofs.some((proof) => proof.split.end === split.end));
|
|
234
|
+
const filed = candidates.filter((event) => event.proofs.some((proof) => proof.before.accn === split.accn || proof.after.accn === split.accn));
|
|
235
|
+
const match = dated.length ? dated : filed;
|
|
236
|
+
if (match.length === 1) eventOf.set(split, match[0]!);
|
|
237
|
+
}
|
|
238
|
+
// The latest disclosed split sets the current basis. Its filings must also be
|
|
239
|
+
// the latest restating filings, or the disclosures and the filings disagree.
|
|
240
|
+
const target = eventOf.get(latest);
|
|
241
|
+
let inconsistent = !!target && events.some((event) => event.consistent && event !== target
|
|
242
|
+
&& cut(event.proofs).firstRestating >= cut(target.proofs).firstRestating);
|
|
112
243
|
const paths = new Map<string, { factor: number; evidence: SecEpsSplitEvidence[] }>();
|
|
113
|
-
const queue = [
|
|
114
|
-
paths.set(
|
|
115
|
-
let inconsistent = false;
|
|
244
|
+
const queue = target ? [`${target.node}:after`] : [];
|
|
245
|
+
if (target) paths.set(queue[0]!, { factor: 1, evidence: [] });
|
|
116
246
|
for (let i = 0; i < queue.length; i++) {
|
|
117
247
|
const node = queue[i]!;
|
|
118
248
|
const path = paths.get(node)!;
|
|
@@ -131,8 +261,7 @@ export function createSecEpsBasisResolver(payload: unknown): (fact: Fact) => { v
|
|
|
131
261
|
return (fact) => {
|
|
132
262
|
if (!fact.end || fact.end >= latest.end!) return { value: fact.val, availableAt: fact.filed };
|
|
133
263
|
const path = fact.accn ? paths.get(fact.accn) : undefined;
|
|
134
|
-
const
|
|
135
|
-
const evidence = [...new Map([...(path?.evidence ?? []), target ?? {
|
|
264
|
+
const evidence = [...new Map([...(path?.evidence ?? []), target?.evidence ?? {
|
|
136
265
|
date: latest.end!, ratio: latest.val!, accessionNumber: latest.accn!, filed: latest.filed!,
|
|
137
266
|
}].map((item) => [item.date, item])).values()].sort((a, b) => a.date.localeCompare(b.date));
|
|
138
267
|
const basis: SecEpsBasis = {
|
|
@@ -140,7 +269,7 @@ export function createSecEpsBasisResolver(payload: unknown): (fact: Fact) => { v
|
|
|
140
269
|
basisDate: latest.end!, evidence,
|
|
141
270
|
};
|
|
142
271
|
// Any unproved intervening disclosure can represent an additional share basis.
|
|
143
|
-
const unproved =
|
|
272
|
+
const unproved = splits.some((split) => split.end! > fact.end! && !eventOf.has(split));
|
|
144
273
|
if (!path || !target || inconsistent || unproved || !date(fact.filed)) return { basis };
|
|
145
274
|
const value = fact.val! / path.factor;
|
|
146
275
|
if (!Number.isFinite(value)) return { basis };
|
package/src/utils/sec.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TickerRecord } from "../types/ticker";
|
|
2
|
+
import { tickerHasListingSuffix } from "../sources/listing-symbols";
|
|
2
3
|
import { canonicalExchange, parsePublicTickerKey, US_LISTING_EXCHANGES } from "./exchanges";
|
|
3
4
|
|
|
4
5
|
/** Canonical US equity venues: the listing exchanges plus other lit and OTC venues. */
|
|
@@ -39,40 +40,36 @@ function isFundType(value?: string): boolean {
|
|
|
39
40
|
return FUND_TYPES.has(normalize(value).replace(/[\s_-]/g, ""));
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Whether an equity SEC pane (filings, insider transactions) can show the
|
|
45
|
+
* ticker: an equity, or a type not filled in yet, that is not known to be
|
|
46
|
+
* listed outside the US. A ticker whose venue and currency were never filled
|
|
47
|
+
* in, as when its quote is unavailable, is looked up as a US ticker, as the
|
|
48
|
+
* SEC issuer lookup does with a symbol that names no venue.
|
|
49
|
+
*/
|
|
50
|
+
export function mayBeUsEquityTicker(ticker: TickerRecord | null | undefined): boolean {
|
|
51
|
+
return mayBeUsListingOfType(ticker, isEquityType);
|
|
44
52
|
}
|
|
45
53
|
|
|
46
|
-
/**
|
|
47
|
-
export function
|
|
48
|
-
return
|
|
54
|
+
/** The same for a view that also lists a US-listed fund's filings, which funds file with the SEC. */
|
|
55
|
+
export function mayBeUsEquityOrFundTicker(ticker: TickerRecord | null | undefined): boolean {
|
|
56
|
+
return mayBeUsListingOfType(ticker, (type) => isEquityType(type) || isFundType(type));
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
function
|
|
59
|
+
function mayBeUsListingOfType(
|
|
52
60
|
ticker: TickerRecord | null | undefined,
|
|
53
61
|
acceptsType: (type?: string) => boolean,
|
|
54
62
|
): boolean {
|
|
55
63
|
if (!ticker) return false;
|
|
56
|
-
|
|
57
64
|
const primaryContract = ticker.metadata.broker_contracts?.[0];
|
|
58
|
-
|
|
59
|
-
const currency = normalize(primaryContract?.currency ?? ticker.metadata.currency);
|
|
60
|
-
const exchangeCandidates = [
|
|
61
|
-
primaryContract?.primaryExchange,
|
|
62
|
-
primaryContract?.exchange,
|
|
63
|
-
ticker.metadata.exchange,
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
// A listing saved without a known currency counts by its venue.
|
|
67
|
-
return acceptsType(type)
|
|
68
|
-
&& (currency === "USD" || !currency)
|
|
69
|
-
&& exchangeCandidates.some((exchange) => isUsExchange(exchange));
|
|
65
|
+
return acceptsType(primaryContract?.secType ?? ticker.metadata.assetCategory) && !isKnownNonUsListing(ticker);
|
|
70
66
|
}
|
|
71
67
|
|
|
72
68
|
/**
|
|
73
|
-
* The metadata places the listing outside the US: a non-USD currency,
|
|
74
|
-
*
|
|
75
|
-
*
|
|
69
|
+
* The metadata places the listing outside the US: a non-USD currency, venues
|
|
70
|
+
* none of which is a US exchange, or, with no venue, a symbol that names one
|
|
71
|
+
* abroad (SAN:EPA, VOD.L, 7203.T). A missing currency or venue is unknown
|
|
72
|
+
* rather than foreign. SEC filings, FINRA short interest, 13F and
|
|
76
73
|
* congressional disclosures only cover US listings.
|
|
77
74
|
*/
|
|
78
75
|
export function isKnownNonUsListing(ticker: TickerRecord | null | undefined): boolean {
|
|
@@ -83,23 +80,20 @@ export function isKnownNonUsListing(ticker: TickerRecord | null | undefined): bo
|
|
|
83
80
|
const venues = [primaryContract?.primaryExchange, primaryContract?.exchange, ticker.metadata.exchange]
|
|
84
81
|
.map(normalize)
|
|
85
82
|
.filter((exchange) => exchange.length > 0 && !ROUTING_EXCHANGES.has(exchange));
|
|
86
|
-
|
|
83
|
+
if (venues.length > 0) return !venues.some((exchange) => isUsExchange(exchange));
|
|
84
|
+
const symbol = parsePublicTickerKey(ticker.metadata.ticker ?? "");
|
|
85
|
+
if (symbol.exchange) return !ROUTING_EXCHANGES.has(symbol.exchange) && !isUsExchange(symbol.exchange);
|
|
86
|
+
return tickerHasListingSuffix(symbol.symbol);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
|
-
* The metadata shows the ticker is not a US equity: a non-
|
|
91
|
-
*
|
|
92
|
-
* unknown rather than foreign, e.g. an unsaved symbol
|
|
93
|
-
*
|
|
90
|
+
* The metadata shows the ticker is not a US equity: a non-equity type or a
|
|
91
|
+
* listing known to be outside the US. A ticker with no venue or currency is
|
|
92
|
+
* unknown rather than foreign, e.g. an unsaved symbol whose quote is
|
|
93
|
+
* unavailable.
|
|
94
94
|
*/
|
|
95
95
|
export function isKnownNonUsEquityTicker(ticker: TickerRecord | null | undefined): boolean {
|
|
96
|
-
|
|
97
|
-
const primaryContract = ticker.metadata.broker_contracts?.[0];
|
|
98
|
-
const currency = normalize(primaryContract?.currency ?? ticker.metadata.currency);
|
|
99
|
-
if (currency && currency !== "USD") return true;
|
|
100
|
-
if (!isEquityType(primaryContract?.secType ?? ticker.metadata.assetCategory)) return true;
|
|
101
|
-
return [primaryContract?.primaryExchange, primaryContract?.exchange, ticker.metadata.exchange]
|
|
102
|
-
.some((exchange) => normalize(exchange).length > 0);
|
|
96
|
+
return !!ticker && !mayBeUsEquityTicker(ticker);
|
|
103
97
|
}
|
|
104
98
|
|
|
105
99
|
/**
|
|
@@ -114,6 +108,16 @@ export function nonUsSecListingVenue(ticker: string, exchange?: string): string
|
|
|
114
108
|
return venue;
|
|
115
109
|
}
|
|
116
110
|
|
|
111
|
+
/**
|
|
112
|
+
* The key an issuer read is cached and deduplicated under: the ticker as given
|
|
113
|
+
* for a US listing or a symbol with no venue, SYMBOL:VENUE for a listing
|
|
114
|
+
* elsewhere, so SAN in Paris and SAN in New York never share an entry.
|
|
115
|
+
*/
|
|
116
|
+
export function secListingKey(ticker: string, exchange?: string): string {
|
|
117
|
+
const venue = nonUsSecListingVenue(ticker, exchange);
|
|
118
|
+
return venue ? `${parsePublicTickerKey(ticker).symbol}:${venue}` : normalize(ticker);
|
|
119
|
+
}
|
|
120
|
+
|
|
117
121
|
/**
|
|
118
122
|
* The item codes in EDGAR's `items` field ("2.02,9.01"). EDGAR fills the same
|
|
119
123
|
* field with other values for some forms, such as the order dates of a CT
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* const data = await client.fetch("https://api.example.com/data");
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { abortError, signalWithTimeout } from "./async-deadline";
|
|
10
11
|
import { httpFetch } from "./http-transport";
|
|
11
12
|
|
|
12
13
|
const DEFAULT_REQUESTS_PER_MINUTE = 30;
|
|
@@ -17,9 +18,9 @@ const BACKOFF_BASE_MS = 1_000;
|
|
|
17
18
|
export interface ThrottledFetchOptions {
|
|
18
19
|
/** Max requests per minute per host. Default: 30 */
|
|
19
20
|
requestsPerMinute?: number;
|
|
20
|
-
/** Max retries on 429
|
|
21
|
+
/** Max retries on 429, 5xx, timeouts and network errors. Default: 2 */
|
|
21
22
|
maxRetries?: number;
|
|
22
|
-
/**
|
|
23
|
+
/** Timeout of each attempt in ms, also when the caller passes a signal. Default: 10000 */
|
|
23
24
|
timeoutMs?: number;
|
|
24
25
|
/** Initial retry backoff in ms. Default: 1000 */
|
|
25
26
|
backoffBaseMs?: number;
|
|
@@ -103,33 +104,63 @@ export function createThrottledFetch(
|
|
|
103
104
|
return Math.max(retryAfterMs, backoff);
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
// Retry rule: a timeout or network error is retried while retries remain,
|
|
108
|
+
// unless the caller's own signal has aborted. Once the caller aborted, the
|
|
109
|
+
// request ends with their abort reason: no further attempt and no backoff,
|
|
110
|
+
// including while waiting on the rate limit or a backoff. An AbortError that
|
|
111
|
+
// is not the caller's can only come from the attempt's timeout (or the
|
|
112
|
+
// transport giving up), so it counts as a timeout.
|
|
113
|
+
function isRetryableFetchError(error: unknown): boolean {
|
|
110
114
|
const detail =
|
|
111
115
|
error instanceof Error
|
|
112
116
|
? `${error.name} ${error.message} ${String((error as { code?: unknown }).code ?? "")}`
|
|
113
117
|
: String(error);
|
|
114
|
-
return (
|
|
115
|
-
|
|
116
|
-
detail,
|
|
117
|
-
) ||
|
|
118
|
-
(retryAbortError && /AbortError/i.test(detail))
|
|
118
|
+
return /TimeoutError|AbortError|ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|socket connection|socket hang up|fetch failed|network connection|connection closed/i.test(
|
|
119
|
+
detail,
|
|
119
120
|
);
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
function abortReason(signal: AbortSignal): unknown {
|
|
124
|
+
return signal.reason ?? abortError("The operation was aborted.");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function throwIfAborted(signal: AbortSignal | null | undefined): void {
|
|
128
|
+
if (signal?.aborted) throw abortReason(signal);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Waits `ms`, or rejects with the caller's abort reason as soon as the signal fires. */
|
|
132
|
+
function wait(ms: number, signal: AbortSignal | null | undefined): Promise<void> {
|
|
133
|
+
return new Promise<void>((resolve, reject) => {
|
|
134
|
+
if (signal?.aborted) {
|
|
135
|
+
reject(abortReason(signal));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const onAbort = () => {
|
|
139
|
+
clearTimeout(timer);
|
|
140
|
+
reject(abortReason(signal!));
|
|
141
|
+
};
|
|
142
|
+
const timer = setTimeout(() => {
|
|
143
|
+
signal?.removeEventListener("abort", onAbort);
|
|
144
|
+
resolve();
|
|
145
|
+
}, ms);
|
|
146
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
122
150
|
async function executeRequest(
|
|
123
151
|
url: string,
|
|
124
152
|
init: RequestInit | undefined,
|
|
125
153
|
retriesLeft: number,
|
|
126
154
|
): Promise<Response> {
|
|
155
|
+
const callerSignal = init?.signal;
|
|
156
|
+
throwIfAborted(callerSignal);
|
|
157
|
+
|
|
127
158
|
const host = getHost(url);
|
|
128
159
|
|
|
129
160
|
// Wait for rate limit window
|
|
130
161
|
const delay = getDelayMs(host);
|
|
131
162
|
if (delay > 0) {
|
|
132
|
-
await
|
|
163
|
+
await wait(delay, callerSignal);
|
|
133
164
|
}
|
|
134
165
|
|
|
135
166
|
recordRequest(host);
|
|
@@ -140,17 +171,16 @@ export function createThrottledFetch(
|
|
|
140
171
|
...defaultHeaders,
|
|
141
172
|
...((init?.headers as Record<string, string>) ?? {}),
|
|
142
173
|
},
|
|
143
|
-
signal:
|
|
174
|
+
signal: signalWithTimeout(callerSignal ?? undefined, timeoutMs),
|
|
144
175
|
};
|
|
145
176
|
|
|
146
177
|
let resp: Response;
|
|
147
178
|
try {
|
|
148
179
|
resp = await fetchTransport(url, mergedInit);
|
|
149
180
|
} catch (error) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
);
|
|
181
|
+
throwIfAborted(callerSignal);
|
|
182
|
+
if (retriesLeft > 0 && isRetryableFetchError(error)) {
|
|
183
|
+
await wait(getBackoffMs(retriesLeft), callerSignal);
|
|
154
184
|
return executeRequest(url, init, retriesLeft - 1);
|
|
155
185
|
}
|
|
156
186
|
throw error;
|
|
@@ -158,11 +188,10 @@ export function createThrottledFetch(
|
|
|
158
188
|
|
|
159
189
|
// Retry on 429 or 5xx
|
|
160
190
|
if ((resp.status === 429 || resp.status >= 500) && retriesLeft > 0) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
),
|
|
191
|
+
throwIfAborted(callerSignal);
|
|
192
|
+
await wait(
|
|
193
|
+
getBackoffMs(retriesLeft, resp.headers.get("retry-after")),
|
|
194
|
+
callerSignal,
|
|
166
195
|
);
|
|
167
196
|
return executeRequest(url, init, retriesLeft - 1);
|
|
168
197
|
}
|