gloomberb 0.18.1 → 0.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +106 -5
- package/src/api-client/commodity-balances.ts +6 -0
- package/src/api-client/company-kpis.ts +9 -0
- package/src/api-client/crypto-markets.ts +9 -0
- package/src/api-client/data.ts +21 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/index.ts +2 -0
- package/src/api-client/request.ts +5 -3
- package/src/api-client/telemetry.ts +17 -0
- package/src/api-client/types.ts +5 -0
- package/src/app/global-shortcuts.ts +1 -16
- package/src/app/pane-runtime/plugin-bindings.ts +3 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
- package/src/app/runtime/plugin-bindings.ts +6 -2
- package/src/app/runtime/update.ts +28 -8
- package/src/architecture/pane-tabs-allowlist.json +65 -0
- package/src/brokers/logos.ts +66 -0
- package/src/brokers/profile-form.ts +3 -0
- package/src/brokers/remote-broker-adapter.ts +13 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/service-capabilities.ts +7 -1
- package/src/cli/base-converter.ts +60 -18
- package/src/cli/commands/market.ts +354 -187
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +167 -60
- package/src/cli/currency-hints.ts +27 -0
- package/src/cli/fx-pair.ts +41 -4
- package/src/cli/glossary.ts +57 -4
- package/src/cli/help.ts +1 -1
- package/src/cli/helpers.ts +36 -3
- package/src/cli/index.ts +12 -1
- package/src/cli/issuer-membership.ts +103 -0
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +51 -3
- package/src/cli/name-entries.ts +51 -12
- package/src/cli/pane-functions/capabilities.ts +2 -0
- package/src/cli/pane-functions/catalog.ts +19 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +11 -5
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +27 -3
- package/src/cli/pane-functions/headless.ts +31 -11
- package/src/cli/pane-functions/index.ts +67 -7
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +21 -6
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/report-tables.ts +136 -18
- package/src/cli/result.ts +12 -4
- package/src/cli/series-search.ts +2 -1
- package/src/cli/symbol-list.ts +47 -11
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +25 -7
- package/src/components/chart/composite/drawing-store.ts +26 -6
- package/src/components/chart/composite/format.ts +13 -1
- package/src/components/chart/composite/interactions.ts +2 -1
- package/src/components/chart/composite/level-labels.ts +30 -0
- package/src/components/chart/composite/levels.ts +17 -6
- package/src/components/chart/composite/panel-surface.tsx +69 -8
- package/src/components/chart/composite/types.ts +6 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +1 -1
- package/src/components/command-bar/routes/root/command-items.ts +32 -6
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/data-table/view.tsx +23 -3
- package/src/components/index.ts +3 -0
- package/src/components/layout/detached-pane-shell.tsx +1 -0
- package/src/components/layout/floating-pane.tsx +1 -0
- package/src/components/layout/header.tsx +228 -55
- package/src/components/layout/pane/content.tsx +16 -14
- package/src/components/layout/pane/footer/index.tsx +22 -3
- package/src/components/layout/pane/footer/model.ts +99 -4
- package/src/components/layout/pane/header-tabs.tsx +15 -1
- package/src/components/layout/pane/header.tsx +10 -3
- package/src/components/layout/pane/index.tsx +1 -0
- package/src/components/layout/pane/pane-tabs.tsx +9 -1
- package/src/components/onboarding/onboarding-frame.tsx +9 -11
- package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
- package/src/components/onboarding/wizard-model.ts +10 -3
- package/src/components/ui/button.tsx +11 -4
- package/src/components/ui/choice-dialog.tsx +8 -5
- package/src/components/ui/display.tsx +11 -6
- package/src/components/ui/fields.tsx +100 -20
- package/src/components/ui/icon.tsx +6 -1
- package/src/components/ui/list-row-lines.tsx +87 -0
- package/src/components/ui/list-view.tsx +14 -0
- package/src/components/ui/logo-mark.tsx +64 -0
- package/src/components/ui/number-step.ts +54 -0
- package/src/components/ui/select-button.tsx +5 -4
- package/src/components/ui/select-field.tsx +3 -3
- package/src/components/ui/stat-grid.tsx +28 -3
- package/src/components/ui/status.tsx +20 -4
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- package/src/core/app-services.ts +4 -0
- package/src/core/state/app/instrument.ts +8 -3
- package/src/data/config/store/normalize.ts +4 -1
- package/src/data/plugin-cache.ts +32 -6
- package/src/docs/snapshot.json +5 -5
- package/src/i18n/es.ts +31 -3
- package/src/i18n/ja.ts +31 -3
- package/src/i18n/ko.ts +31 -3
- package/src/i18n/zh-cn.ts +32 -4
- package/src/i18n/zh-tw.ts +32 -4
- package/src/market-data/commodity-names.ts +80 -0
- package/src/market-data/coordinator/auxiliary.ts +3 -1
- package/src/market-data/coordinator/chart.ts +3 -1
- package/src/market-data/futures-board.ts +38 -1
- package/src/market-data/futures-contract.ts +91 -0
- package/src/market-data/fx-closes.ts +78 -1
- package/src/market-data/market/change-basis.ts +219 -0
- package/src/market-data/market/format.ts +28 -12
- package/src/market-data/market/freshness.ts +38 -0
- package/src/market-data/market/status.ts +55 -9
- package/src/market-data/market/year-to-date.ts +46 -0
- package/src/market-data/quotes/contributions.ts +12 -0
- package/src/market-data/quotes/delisting.ts +34 -0
- package/src/market-data/quotes/live-valuation.ts +23 -0
- package/src/market-data/quotes/resolution.ts +8 -2
- package/src/market-data/tax-equivalent.ts +10 -2
- package/src/notifications/app-notifier.ts +2 -2
- package/src/notifications/notification-log.ts +8 -6
- package/src/plugins/builtin/alerts/alert-engine.ts +3 -1
- package/src/plugins/builtin/alerts/cli.ts +26 -18
- package/src/plugins/builtin/alerts/events.ts +1 -0
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/alerts/research-rules.ts +1 -0
- package/src/plugins/builtin/analytics/risk-pane.tsx +10 -7
- package/src/plugins/builtin/attention/pane.tsx +16 -14
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/awards/pane.tsx +6 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- package/src/plugins/builtin/backtest/export.ts +1 -1
- package/src/plugins/builtin/backtest/window.ts +16 -3
- package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
- package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
- package/src/plugins/builtin/broker-manager/index.tsx +20 -9
- package/src/plugins/builtin/broker-manager/model.ts +5 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
- package/src/plugins/builtin/broker-manager/table.ts +5 -1
- package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
- package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
- package/src/plugins/builtin/broker-trading/command.ts +103 -0
- package/src/plugins/builtin/broker-trading/controller.ts +688 -0
- package/src/plugins/builtin/broker-trading/dock.tsx +184 -0
- package/src/plugins/builtin/broker-trading/headless.ts +76 -0
- package/src/plugins/builtin/broker-trading/index.tsx +77 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
- package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
- package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
- package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
- package/src/plugins/builtin/broker-trading/pane.tsx +507 -0
- package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
- package/src/plugins/builtin/broker-trading/ticket.tsx +578 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
- package/src/plugins/builtin/buildout/format.ts +1 -0
- package/src/plugins/builtin/buildout/model/index.ts +16 -7
- package/src/plugins/builtin/buildout/pane/index.tsx +11 -7
- package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
- package/src/plugins/builtin/catalysts/client.ts +2 -2
- package/src/plugins/builtin/catalysts/pane.tsx +3 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
- package/src/plugins/builtin/chart-composer/headless.ts +2 -1
- package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +32 -11
- package/src/plugins/builtin/chart-composer/presets.ts +2 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
- package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
- package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
- package/src/plugins/builtin/commodity-balances/client.ts +6 -4
- package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
- package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
- package/src/plugins/builtin/commodity-balances/model.ts +22 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +39 -9
- package/src/plugins/builtin/company-attention/app-rank.tsx +4 -5
- package/src/plugins/builtin/company-attention/pane.tsx +13 -11
- package/src/plugins/builtin/company-kpis/client.ts +3 -2
- package/src/plugins/builtin/company-kpis/headless.ts +5 -7
- package/src/plugins/builtin/company-kpis/model.ts +85 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +30 -15
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- package/src/plugins/builtin/correlation/headless.ts +2 -1
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -0
- package/src/plugins/builtin/correlation/settings.ts +1 -0
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cot/index.tsx +1 -1
- package/src/plugins/builtin/cot/model.ts +35 -12
- package/src/plugins/builtin/cot/pane.tsx +4 -3
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
- package/src/plugins/builtin/credit-documents/pane.tsx +8 -7
- package/src/plugins/builtin/crypto-board/client.ts +33 -2
- package/src/plugins/builtin/crypto-board/headless.ts +5 -2
- package/src/plugins/builtin/crypto-board/model.ts +28 -8
- package/src/plugins/builtin/debt-maturities/pane.tsx +4 -3
- package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/doe/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
- package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
- package/src/plugins/builtin/econ/calendar-model.ts +35 -0
- package/src/plugins/builtin/econ/index.tsx +31 -4
- package/src/plugins/builtin/econ/print-watch.ts +279 -0
- package/src/plugins/builtin/equity-screener/headless.ts +30 -5
- package/src/plugins/builtin/equity-screener/model.ts +68 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +129 -119
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
- package/src/plugins/builtin/exposure/client.ts +4 -1
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/exposure/evidence.ts +4 -1
- package/src/plugins/builtin/exposure/holdings.ts +3 -1
- package/src/plugins/builtin/exposure/pane.tsx +43 -21
- package/src/plugins/builtin/filing-events/index.tsx +9 -3
- package/src/plugins/builtin/futures/table.tsx +6 -3
- package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +4 -1
- package/src/plugins/builtin/gpu/changes.tsx +10 -10
- package/src/plugins/builtin/gpu/equities.tsx +4 -1
- package/src/plugins/builtin/gpu/history.tsx +4 -2
- package/src/plugins/builtin/gpu/pane.tsx +5 -3
- package/src/plugins/builtin/holders/client.ts +2 -1
- package/src/plugins/builtin/holders/headless.ts +2 -1
- package/src/plugins/builtin/holders/thirteenf-match.ts +3 -1
- package/src/plugins/builtin/insider/base-values.ts +76 -0
- package/src/plugins/builtin/insider/client.ts +3 -0
- package/src/plugins/builtin/insider/headless.ts +48 -10
- package/src/plugins/builtin/insider/index.tsx +17 -8
- package/src/plugins/builtin/insider/issuer.ts +34 -4
- package/src/plugins/builtin/insider/signals-client.ts +2 -2
- package/src/plugins/builtin/insider/table-model.ts +35 -13
- package/src/plugins/builtin/insider/table.tsx +5 -2
- package/src/plugins/builtin/iv-history/events.ts +145 -0
- package/src/plugins/builtin/iv-history/format.ts +3 -0
- package/src/plugins/builtin/iv-history/headless.ts +91 -7
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- package/src/plugins/builtin/jodi/pane.tsx +4 -4
- package/src/plugins/builtin/market-halts/model.ts +31 -6
- package/src/plugins/builtin/market-halts/pane.tsx +3 -0
- package/src/plugins/builtin/market-movers/client.ts +4 -3
- package/src/plugins/builtin/market-movers/screener.ts +4 -3
- package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/notes/cli.ts +1 -1
- package/src/plugins/builtin/notes/export.ts +17 -6
- package/src/plugins/builtin/notes/files.ts +16 -5
- package/src/plugins/builtin/options/enrichment-model.ts +10 -9
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/model-spot.ts +70 -4
- package/src/plugins/builtin/options/view.tsx +118 -35
- package/src/plugins/builtin/options-calculator/pane.tsx +7 -4
- package/src/plugins/builtin/options-positioning/headless.ts +11 -3
- package/src/plugins/builtin/options-positioning/model.ts +24 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +10 -12
- package/src/plugins/builtin/options-scenario/client.ts +19 -1
- package/src/plugins/builtin/options-scenario/headless.ts +13 -6
- package/src/plugins/builtin/options-scenario/pane.tsx +54 -11
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +5 -4
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -5
- package/src/plugins/builtin/portfolio-list/cli/render.ts +34 -10
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- package/src/plugins/builtin/power/client.ts +2 -2
- package/src/plugins/builtin/power/pane.tsx +36 -31
- package/src/plugins/builtin/primary-dealers/client.ts +137 -62
- package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
- package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
- package/src/plugins/builtin/primary-dealers/model.ts +179 -129
- package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
- package/src/plugins/builtin/research/analyst-model.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/client.ts +2 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +4 -3
- package/src/plugins/builtin/risk-factors/index.tsx +12 -3
- package/src/plugins/builtin/scanner/feed.ts +6 -1
- package/src/plugins/builtin/scanner/headless.ts +1 -1
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/client.ts +2 -1
- package/src/plugins/builtin/sectors/client.ts +4 -3
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- package/src/plugins/builtin/shared/cloud-resource.ts +15 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
- package/src/plugins/builtin/shared/free-routes.ts +24 -0
- package/src/plugins/builtin/shared/headless-options.ts +11 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +64 -0
- package/src/plugins/builtin/shared/series-list-detail.tsx +3 -1
- package/src/plugins/builtin/shared/volatility/options.ts +2 -1
- package/src/plugins/builtin/social-mentions/client.ts +2 -1
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -2
- package/src/plugins/builtin/themes/model.ts +43 -10
- package/src/plugins/builtin/themes/pane.tsx +8 -6
- package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
- package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
- package/src/plugins/builtin/time-sales/pane.tsx +2 -2
- package/src/plugins/builtin/trace-bonds/client.ts +14 -3
- package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
- package/src/plugins/builtin/vol-surface/client.ts +23 -3
- package/src/plugins/builtin/vol-surface/headless.ts +33 -14
- package/src/plugins/builtin/vol-surface/model.ts +30 -10
- package/src/plugins/builtin/vol-surface/pane.tsx +35 -15
- package/src/plugins/builtin/vol-surface/stored.ts +34 -3
- package/src/plugins/builtin/vol-surface/tables.ts +14 -0
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/compat.ts +3 -0
- package/src/plugins/external-runtime.ts +5 -0
- package/src/plugins/loader.ts +6 -0
- package/src/plugins/plugin-icon.ts +61 -0
- package/src/plugins/registry/host-actions.ts +2 -1
- package/src/plugins/registry/index.ts +9 -1
- package/src/plugins/runtime/context.tsx +5 -0
- package/src/plugins/runtime/index.tsx +3 -0
- package/src/portfolio/column-values.ts +13 -4
- package/src/portfolio/cost-fx.ts +5 -28
- package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
- package/src/public/broker.ts +8 -0
- package/src/public/market-data.ts +5 -0
- package/src/renderers/dom/desktop/control-styles.ts +7 -1
- package/src/renderers/dom/desktop/controls.tsx +40 -8
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/list-view.tsx +51 -19
- package/src/renderers/dom/desktop/menu.tsx +10 -1
- package/src/renderers/dom/desktop/query-bar.tsx +2 -0
- package/src/renderers/dom/desktop/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +22 -5
- package/src/renderers/dom/host/top-surface.ts +127 -0
- package/src/renderers/dom/styles.css +51 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +21 -1
- package/src/renderers/electrobun/bun/desktop/backend.ts +10 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +210 -30
- package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
- package/src/renderers/electrobun/view/external-plugins.ts +1 -0
- package/src/renderers/electrobun/view/news-source.ts +4 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +8 -1
- package/src/renderers/opentui/start.tsx +7 -0
- package/src/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/index.ts +57 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/provider-errors.ts +3 -1
- package/src/sources/provider-router/history.ts +49 -6
- package/src/sources/provider-router/index.ts +18 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +62 -11
- package/src/sync/local-only.ts +121 -0
- package/src/sync/react.ts +6 -0
- package/src/sync/types.ts +3 -0
- package/src/telemetry/crash-reports.ts +4 -0
- package/src/telemetry/update-failures-node.ts +59 -0
- package/src/telemetry/update-failures.ts +307 -0
- package/src/tickers/company-name-listing.ts +9 -7
- package/src/tickers/company-name.ts +43 -0
- package/src/tickers/listing-fate.ts +57 -8
- package/src/tickers/search/index.ts +24 -3
- package/src/time-series/price-comparison.ts +35 -0
- package/src/time-series/resolve.ts +52 -11
- package/src/time-series/spec.ts +1 -1
- package/src/time-series/types.ts +5 -0
- package/src/types/broker.ts +56 -1
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +11 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +19 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/issuer-listings.ts +28 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/updater/failure.ts +163 -0
- package/src/updater/index.ts +87 -23
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +28 -1
- package/src/utils/currency-units.ts +32 -1
- package/src/utils/dom-screenshot.ts +22 -9
- package/src/utils/exchange-notation.generated.ts +360 -0
- package/src/utils/exchanges.ts +15 -322
- package/src/utils/format.ts +31 -1
- package/src/utils/instrument-identity.ts +38 -1
- package/src/utils/options.ts +46 -1
- package/src/utils/price-history.ts +4 -0
- package/src/utils/reported-money.ts +8 -4
- package/src/utils/sec.ts +68 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { HeadlessPaneDefinition } from "../../../types/plugin";
|
|
2
2
|
import type { HeadlessPaneColumn, HeadlessPaneContext } from "../../../types/headless";
|
|
3
3
|
import {
|
|
4
|
+
KPI_FIELDS,
|
|
4
5
|
NUMERIC_FIELDS,
|
|
5
6
|
type NumericField,
|
|
6
7
|
type ScreenRow,
|
|
7
8
|
} from "../../../api-client/equity-screener";
|
|
9
|
+
import { KPI_COVERAGE_NOTE } from "../company-kpis/model";
|
|
8
10
|
import { EXTENDED_SESSION_LABELS, type ExtendedSession } from "../../../market-data/market/status";
|
|
9
11
|
import { buildQuoteKey } from "../../../market-data/selectors";
|
|
10
12
|
import type { Quote } from "../../../types/financials";
|
|
@@ -17,8 +19,12 @@ import {
|
|
|
17
19
|
formatScreenValue,
|
|
18
20
|
metricDate,
|
|
19
21
|
parseScreenDefinition,
|
|
22
|
+
screenColumnLabel,
|
|
20
23
|
screenDefinitionSyntax,
|
|
21
|
-
|
|
24
|
+
screenCurrencyEmpty,
|
|
25
|
+
screenLabel,
|
|
26
|
+
screenYtdBase,
|
|
27
|
+
withheldMetricNote,
|
|
22
28
|
} from "./model";
|
|
23
29
|
import type { ScreenDefinition } from "../../../api-client/equity-screener";
|
|
24
30
|
|
|
@@ -128,15 +134,33 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
128
134
|
// A sort on one of them is redone on those figures.
|
|
129
135
|
const rows = sortLiveScreenRows(overlayScreenQuotes(data.rows, await loadRowQuotes(data.rows, ctx)), data.definition.sort);
|
|
130
136
|
const extendedSessions = screenExtendedSessions(rows);
|
|
137
|
+
// A Pro metric the server held back from Free says so once, as KPIS says it; a KPI
|
|
138
|
+
// with gaps says what a gap is, as KPIS does.
|
|
139
|
+
const withheld = rows.some((row) => row.metrics[metric].access === "pro");
|
|
140
|
+
const errors = withheld ? [...data.warnings, withheldMetricNote(metric)] : data.warnings;
|
|
141
|
+
const missing = rows.filter((row) => row.metrics[metric].value === null).length;
|
|
142
|
+
const kpiGaps = !withheld && missing > 0 && (KPI_FIELDS as readonly string[]).includes(metric)
|
|
143
|
+
? [`${missing} of ${rows.length} ${rows.length === 1 ? "row has" : "rows have"} no ${screenLabel(metric)}. ${KPI_COVERAGE_NOTE}`]
|
|
144
|
+
: [];
|
|
145
|
+
// A CAD screen of listings quoted in USD is empty for its currency, not for want of companies: say so, and give the screen in USD.
|
|
146
|
+
const currencyEmpty = rows.length === 0 ? screenCurrencyEmpty(data.definition, data.universe) : null;
|
|
147
|
+
const empty = currencyEmpty
|
|
148
|
+
? [
|
|
149
|
+
`${currencyEmpty.title} ${currencyEmpty.detail}`,
|
|
150
|
+
`In the base currency, ${currencyEmpty.base.currency}: gloomberb fn EQS --definition '${spacedJson(currencyEmpty.base)}'${metric === "marketCap" ? "" : ` --metric ${metric}`}`,
|
|
151
|
+
].join("\n")
|
|
152
|
+
: null;
|
|
131
153
|
// Payload status also counts ordinary per-field sparsity; only generation
|
|
132
154
|
// warnings are failures, and more pages are not an error.
|
|
133
155
|
return {
|
|
134
156
|
freshness: { asOf: data.snapshot?.sourceNewestAt ?? null },
|
|
135
|
-
complete: !data.nextCursor &&
|
|
136
|
-
errors
|
|
157
|
+
complete: !data.nextCursor && errors.length === 0,
|
|
158
|
+
errors,
|
|
159
|
+
...(kpiGaps.length ? { notes: kpiGaps } : {}),
|
|
137
160
|
sections: [
|
|
138
161
|
{
|
|
139
162
|
title: "Matching equities",
|
|
163
|
+
...(empty ? { empty } : {}),
|
|
140
164
|
columns: [
|
|
141
165
|
{ key: "symbol", header: "Symbol" },
|
|
142
166
|
{ key: "exchange", header: "Exchange" },
|
|
@@ -145,7 +169,8 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
145
169
|
...(metric === "price" ? [] : [{ key: "price", header: "Last", align: "right" as const, format: price }]),
|
|
146
170
|
...(metric === "changePercent" ? [] : [{ key: "changePercent", header: "Chg%", align: "right" as const, format: percent }]),
|
|
147
171
|
...extendedSessions.map(extendedColumn),
|
|
148
|
-
|
|
172
|
+
// YTD names the close it is measured from: `YTD% vs 31 Dec close`.
|
|
173
|
+
{ key: "value", header: screenColumnLabel(metric, screenYtdBase(data.rows, data.snapshot?.assembledAt), "report"), align: "right" },
|
|
149
174
|
{ key: "unit", header: "Unit" },
|
|
150
175
|
{ key: "percentile", header: "Covered pctl", align: "right" },
|
|
151
176
|
{ key: "asOf", header: "As of" },
|
|
@@ -172,7 +197,7 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
172
197
|
? null
|
|
173
198
|
: Math.round(observation.percentile.value),
|
|
174
199
|
asOf: stamp.collected ? `${stamp.text} (collected)` : stamp.text,
|
|
175
|
-
state: observation.state,
|
|
200
|
+
state: observation.access === "pro" ? "locked" : observation.state,
|
|
176
201
|
};
|
|
177
202
|
}),
|
|
178
203
|
},
|
|
@@ -17,9 +17,11 @@ import {
|
|
|
17
17
|
type ScreenPayload,
|
|
18
18
|
type ScreenRow,
|
|
19
19
|
} from "../../../api-client/equity-screener";
|
|
20
|
+
import { yearToDateBaseLabel } from "../../../market-data/market/year-to-date";
|
|
20
21
|
import { canonicalExchange } from "../../../utils/exchanges";
|
|
21
22
|
import { LATEST_DISTRIBUTION, TRAILING_DIVIDEND_BASIS } from "../../../utils/forward-dividend";
|
|
22
23
|
import { formatCompact, formatNumber } from "../../../utils/format";
|
|
24
|
+
import { kpiFreeLimitNote } from "../company-kpis/model";
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* Column headers. Currency sits in the footer; percentage fields carry %.
|
|
@@ -160,8 +162,29 @@ export function resultFields(definition: ScreenDefinition, metric: NumericField)
|
|
|
160
162
|
return [...new Set(fields)].filter((field) => definition.currency || !MONETARY.has(field) || field === metric);
|
|
161
163
|
}
|
|
162
164
|
|
|
165
|
+
/**
|
|
166
|
+
* The close the screen's YTD returns are measured from, `31 Dec`: NYSE's last
|
|
167
|
+
* session of the year before the newest session a row's YTD ends on, the
|
|
168
|
+
* snapshot's day without one.
|
|
169
|
+
*/
|
|
170
|
+
export function screenYtdBase(rows: readonly ScreenRow[], assembledAt?: string | null, now = Date.now()): string | null {
|
|
171
|
+
const day = rows.map((row) => row.metrics.returnYtdPercent?.asOf?.slice(0, 10) ?? "").filter(Boolean).sort().at(-1)
|
|
172
|
+
?? assembledAt?.slice(0, 10);
|
|
173
|
+
return day ? yearToDateBaseLabel(day, "NYSE", now) : null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* A column's header. YTD names the close it is measured from: `YTD% vs 31 Dec
|
|
178
|
+
* close` in a report, `YTD% vs 31 Dec` in the pane and `YTD% 31 Dec` where
|
|
179
|
+
* the pane is short of room.
|
|
180
|
+
*/
|
|
181
|
+
export function screenColumnLabel(field: NumericField, ytdBase: string | null, form: "report" | "pane" | "compact" = "pane"): string {
|
|
182
|
+
if (field !== "returnYtdPercent" || !ytdBase) return SHORT_LABELS[field];
|
|
183
|
+
return form === "compact" ? `YTD% ${ytdBase}` : `YTD% vs ${ytdBase}${form === "report" ? " close" : ""}`;
|
|
184
|
+
}
|
|
185
|
+
|
|
163
186
|
/** Prices get room for a separated six-figure quote (757,398.00). */
|
|
164
|
-
export const columnWidth = (field: NumericField) => Math.max(field === "price" ? 10 : 8,
|
|
187
|
+
export const columnWidth = (field: NumericField, label = SHORT_LABELS[field]) => Math.max(field === "price" ? 10 : 8, label.length);
|
|
165
188
|
|
|
166
189
|
export const DEFAULT_SCREEN: ScreenDefinition = {
|
|
167
190
|
version: 1,
|
|
@@ -169,6 +192,34 @@ export const DEFAULT_SCREEN: ScreenDefinition = {
|
|
|
169
192
|
criteria: [{ field: "marketCap", op: "gte", value: 10_000_000_000 }],
|
|
170
193
|
sort: { field: "marketCap", direction: "desc" },
|
|
171
194
|
};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Why a screen with a currency found nothing, when the currency may be why:
|
|
198
|
+
* `currency` keeps the listings quoted in it and converts nothing, so a CAD
|
|
199
|
+
* screen of listings quoted in USD reads as no company at all. `title` says
|
|
200
|
+
* which listings it kept, `detail` that it converts nothing and what the
|
|
201
|
+
* covered listings are quoted in, and `base` is the same screen in the base
|
|
202
|
+
* currency the default screen uses (USD), to run instead. Null for a screen
|
|
203
|
+
* in that currency or with none.
|
|
204
|
+
*/
|
|
205
|
+
export function screenCurrencyEmpty(
|
|
206
|
+
definition: ScreenDefinition,
|
|
207
|
+
universe: Pick<ScreenPayload["universe"], "covered" | "currencies">,
|
|
208
|
+
): { title: string; detail: string; base: ScreenDefinition } | null {
|
|
209
|
+
const currency = definition.currency;
|
|
210
|
+
const baseCurrency = DEFAULT_SCREEN.currency!;
|
|
211
|
+
if (!currency || currency === baseCurrency) return null;
|
|
212
|
+
const quoted = universe.currencies.filter(Boolean);
|
|
213
|
+
const none = quoted.length > 0 && !quoted.includes(currency);
|
|
214
|
+
return {
|
|
215
|
+
title: none
|
|
216
|
+
? `None of the ${universe.covered.toLocaleString("en-US")} covered listings is quoted in ${currency}.`
|
|
217
|
+
: `No listing quoted in ${currency} matches.`,
|
|
218
|
+
detail: `Currency ${currency} keeps only the listings quoted in ${currency} and converts nothing${none ? `; the covered listings are quoted in ${quoted.join(", ")}` : ""}.`,
|
|
219
|
+
base: { ...definition, currency: baseCurrency },
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
172
223
|
export const screenRowId = (row: Pick<ScreenRow, "symbol" | "exchange">) =>
|
|
173
224
|
`${canonicalExchange(row.exchange)}:${row.symbol}`;
|
|
174
225
|
export const screenLabel = (field: string) =>
|
|
@@ -481,6 +532,20 @@ const unavailableMetric = (): ScreenMetric => ({
|
|
|
481
532
|
state: "unavailable", scope: "reported", reason: "Not provided by this server", sourceUrl: null,
|
|
482
533
|
percentile: { value: null, sampleCount: 0, scope: "covered-universe" },
|
|
483
534
|
});
|
|
535
|
+
/** Company KPIs, guidance, hiring and app attention: the server leaves them out of a screen for a caller without Gloom Pro. */
|
|
536
|
+
const PRO_FIELDS: ReadonlySet<string> = new Set([...KPI_FIELDS, ...HIRING_APP_FIELDS]);
|
|
537
|
+
const withheldMetric = (): ScreenMetric => ({ ...unavailableMetric(), reason: "Needs Gloom Pro", access: "pro" });
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* What a screen says once of a metric the server held back from Free, a
|
|
541
|
+
* KPI's in the words `fn KPIS` uses ("The free screener shows no KPI values;
|
|
542
|
+
* Gloom Pro shows each company's Backlog.").
|
|
543
|
+
*/
|
|
544
|
+
export function withheldMetricNote(field: NumericField): string {
|
|
545
|
+
return (KPI_FIELDS as readonly string[]).includes(field)
|
|
546
|
+
? kpiFreeLimitNote({ metric: screenLabel(field), shown: 0, total: null, guidance: field.startsWith("guide"), where: "The free screener" })
|
|
547
|
+
: `${screenLabel(field)} needs Gloom Pro.`;
|
|
548
|
+
}
|
|
484
549
|
export function validateScreenPayload(value: unknown): ScreenPayload {
|
|
485
550
|
const data = value as ScreenPayload;
|
|
486
551
|
if (
|
|
@@ -523,8 +588,9 @@ export function validateScreenPayload(value: unknown): ScreenPayload {
|
|
|
523
588
|
ids.add(screenRowId(row));
|
|
524
589
|
for (const field of NUMERIC_FIELDS) {
|
|
525
590
|
// A server from before social, research, volatility or dividend fields omits them; they read as unavailable.
|
|
591
|
+
// A Pro field it left out was held back from this caller.
|
|
526
592
|
if (!row.metrics[field] && ([...SOCIAL_FIELDS, ...RESEARCH_FIELDS, ...VOLATILITY_FIELDS, ...ATTENTION_FIELDS, ...HIRING_APP_FIELDS, ...KPI_FIELDS, ...DIVIDEND_FIELDS] as readonly string[]).includes(field))
|
|
527
|
-
row.metrics[field] = unavailableMetric();
|
|
593
|
+
row.metrics[field] = PRO_FIELDS.has(field) ? withheldMetric() : unavailableMetric();
|
|
528
594
|
const metric = row.metrics[field];
|
|
529
595
|
if (
|
|
530
596
|
!metric ||
|
|
@@ -67,10 +67,12 @@ import {
|
|
|
67
67
|
metricDate,
|
|
68
68
|
parseScreenDefinition,
|
|
69
69
|
resultFields,
|
|
70
|
-
SHORT_LABELS,
|
|
71
70
|
SIGN_COLORED,
|
|
71
|
+
screenColumnLabel,
|
|
72
|
+
screenCurrencyEmpty,
|
|
72
73
|
screenLabel,
|
|
73
74
|
screenRowId,
|
|
75
|
+
screenYtdBase,
|
|
74
76
|
} from "./model";
|
|
75
77
|
import { useScreenResults } from "./results";
|
|
76
78
|
import { overlayLiveScreenRows, screenExtendedMove, screenExtendedSessions, sortLiveScreenRows, type LiveScreenRow } from "./live";
|
|
@@ -101,20 +103,19 @@ const EXTENDED_COLUMN_PREFIX = "ext:";
|
|
|
101
103
|
* CHG% for each session in `extendedSessions`, and is added only whole, as
|
|
102
104
|
* the metrics are.
|
|
103
105
|
*/
|
|
104
|
-
function resultColumns(
|
|
106
|
+
export function resultColumns(
|
|
105
107
|
width: number,
|
|
106
108
|
definition: ScreenDefinition,
|
|
107
109
|
metric: NumericField,
|
|
108
110
|
asOf: boolean,
|
|
109
111
|
extendedSessions: readonly ExtendedSession[] = [],
|
|
112
|
+
ytdBase: string | null = null,
|
|
110
113
|
): DataTableColumn[] {
|
|
111
114
|
const [focus, ...others] = resultFields(definition, metric);
|
|
112
|
-
const metricColumn = (field: NumericField): DataTableColumn =>
|
|
113
|
-
|
|
114
|
-
label:
|
|
115
|
-
|
|
116
|
-
align: "right",
|
|
117
|
-
});
|
|
115
|
+
const metricColumn = (field: NumericField, form: "pane" | "compact"): DataTableColumn => {
|
|
116
|
+
const label = screenColumnLabel(field, ytdBase, form);
|
|
117
|
+
return { id: `m:${field}`, label, width: columnWidth(field, label), align: "right" };
|
|
118
|
+
};
|
|
118
119
|
const extendedColumns = extendedSessions.map((session): DataTableColumn => ({
|
|
119
120
|
id: `${EXTENDED_COLUMN_PREFIX}${session}`,
|
|
120
121
|
label: extendedSessionStateLabel(session),
|
|
@@ -122,20 +123,21 @@ function resultColumns(
|
|
|
122
123
|
align: "right",
|
|
123
124
|
}));
|
|
124
125
|
// The extended move reads beside CHG%, or first after the focus metric when that is CHG%.
|
|
125
|
-
const extras = [
|
|
126
|
+
const extras = (form: "pane" | "compact") => [
|
|
126
127
|
...(focus === "changePercent" ? extendedColumns : []),
|
|
127
|
-
...others.flatMap((field) => field === "changePercent" ? [metricColumn(field), ...extendedColumns] : [metricColumn(field)]),
|
|
128
|
+
...others.flatMap((field) => field === "changePercent" ? [metricColumn(field, form), ...extendedColumns] : [metricColumn(field, form)]),
|
|
128
129
|
];
|
|
129
130
|
const build = (
|
|
130
131
|
extra: DataTableColumn[],
|
|
131
132
|
exchange: boolean,
|
|
132
133
|
sector: boolean,
|
|
134
|
+
form: "pane" | "compact",
|
|
133
135
|
): DataTableColumn[] => [
|
|
134
136
|
{ id: "symbol", label: "SYMBOL", width: 8, align: "left" },
|
|
135
137
|
{ id: "name", label: "NAME", width: 18, flexGrow: 1, align: "left" },
|
|
136
138
|
...(exchange ? [{ id: "exchange", label: "EXCH", width: 7, align: "left" as const }] : []),
|
|
137
139
|
...(sector ? [{ id: "sector", label: "SECTOR", width: 22, align: "left" as const }] : []),
|
|
138
|
-
metricColumn(focus
|
|
140
|
+
metricColumn(focus!, form),
|
|
139
141
|
{ id: "percentile", label: "PCTL", width: 4, align: "right" },
|
|
140
142
|
...(asOf ? [{ id: "date", label: "AS OF", width: 10, align: "left" as const }] : []),
|
|
141
143
|
...extra,
|
|
@@ -143,15 +145,15 @@ function resultColumns(
|
|
|
143
145
|
// Measured the way the table draws them (header floor, gaps, lead gaps), so a
|
|
144
146
|
// column is only added when it fits whole.
|
|
145
147
|
const fits = (columns: DataTableColumn[]) => getTableWidth(columns) <= width;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
return build(shown, exchange, sector);
|
|
148
|
+
// Columns are chosen with YTD's short header (`YTD% 31 Dec`), which reads
|
|
149
|
+
// `YTD% vs 31 Dec` when that still fits: naming the close never costs a column.
|
|
150
|
+
const compact = extras("compact");
|
|
151
|
+
let shown = 0;
|
|
152
|
+
while (shown < compact.length && fits(build(compact.slice(0, shown + 1), false, false, "compact"))) shown++;
|
|
153
|
+
const exchange = fits(build(compact.slice(0, shown), true, false, "compact"));
|
|
154
|
+
const sector = exchange && fits(build(compact.slice(0, shown), true, true, "compact"));
|
|
155
|
+
const named = build(extras("pane").slice(0, shown), exchange, sector, "pane");
|
|
156
|
+
return fits(named) ? named : build(compact.slice(0, shown), exchange, sector, "compact");
|
|
155
157
|
}
|
|
156
158
|
const loadFields = () => fetchScreenFields();
|
|
157
159
|
function ScreenDetail({
|
|
@@ -351,7 +353,11 @@ function EquityScreenView({
|
|
|
351
353
|
const staleFocus = snapshotRows.some((row) => row.metrics[metric]?.state === "stale");
|
|
352
354
|
// A pre-market, after-hours or overnight column only while a row has such a print.
|
|
353
355
|
const extendedSessions = screenExtendedSessions(rows);
|
|
354
|
-
|
|
356
|
+
// YTD names the close it is measured from, `31 Dec`.
|
|
357
|
+
const ytdBase = screenYtdBase(snapshotRows, data?.snapshot?.assembledAt);
|
|
358
|
+
// A screen its currency emptied says so, not that no company matches.
|
|
359
|
+
const currencyEmpty = data && data.rows.length === 0 ? screenCurrencyEmpty(data.definition, data.universe) : null;
|
|
360
|
+
const columns = resultColumns(width, definition, metric, staleFocus, extendedSessions, ytdBase);
|
|
355
361
|
const apply = (next: ScreenDefinition) => {
|
|
356
362
|
try {
|
|
357
363
|
setDefinition(parseScreenDefinition(next));
|
|
@@ -886,11 +892,8 @@ function EquityScreenView({
|
|
|
886
892
|
</PaneStatusBody>
|
|
887
893
|
)
|
|
888
894
|
) : (
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
error={!data ? results.error : null}
|
|
892
|
-
subject="equity screen"
|
|
893
|
-
>
|
|
895
|
+
// The metric stays above the results while a new sort loads.
|
|
896
|
+
<>
|
|
894
897
|
{!opened ? (
|
|
895
898
|
<QueryBar
|
|
896
899
|
width={width}
|
|
@@ -915,101 +918,108 @@ function EquityScreenView({
|
|
|
915
918
|
}]}
|
|
916
919
|
/>
|
|
917
920
|
) : null}
|
|
918
|
-
<
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
return move == null
|
|
954
|
-
? { text: "--", color: colors.textDim }
|
|
955
|
-
: { text: formatPercentRaw(move), color: priceColor(move, colors) };
|
|
921
|
+
<PaneStatusBody
|
|
922
|
+
loading={results.loading && !data}
|
|
923
|
+
error={!data ? results.error : null}
|
|
924
|
+
subject="equity screen"
|
|
925
|
+
>
|
|
926
|
+
<DataTableStackView
|
|
927
|
+
columns={columns}
|
|
928
|
+
items={rows}
|
|
929
|
+
focused={focused && !saveForm}
|
|
930
|
+
rootWidth={width}
|
|
931
|
+
rootHeight={bodyHeight - 1}
|
|
932
|
+
scrollRef={tableScroll}
|
|
933
|
+
onBodyScrollActivity={loadMore}
|
|
934
|
+
resetScrollKey={JSON.stringify(definition)}
|
|
935
|
+
visibleRangeKey={JSON.stringify(definition)}
|
|
936
|
+
onVisibleRangeChange={setVisibleRange}
|
|
937
|
+
getItemKey={screenRowId}
|
|
938
|
+
selection={{
|
|
939
|
+
kind: "id",
|
|
940
|
+
selectedId: selectedId ?? (rows[0] ? screenRowId(rows[0]) : null),
|
|
941
|
+
getId: screenRowId,
|
|
942
|
+
onChange: setSelectedId,
|
|
943
|
+
}}
|
|
944
|
+
onActivate={(row) => setOpenId(screenRowId(row))}
|
|
945
|
+
detailOpen={!!opened}
|
|
946
|
+
onBack={() => setOpenId(null)}
|
|
947
|
+
detailTitle={opened?.name ?? opened?.symbol}
|
|
948
|
+
detailContent={
|
|
949
|
+
opened ? (
|
|
950
|
+
<ScreenDetail
|
|
951
|
+
row={opened}
|
|
952
|
+
width={width}
|
|
953
|
+
height={bodyHeight - 2}
|
|
954
|
+
/>
|
|
955
|
+
) : null
|
|
956
956
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
957
|
+
renderCell={(row, column) => {
|
|
958
|
+
if (column.id.startsWith(EXTENDED_COLUMN_PREFIX)) {
|
|
959
|
+
// The extended print's move from the close in PRICE and CHG%.
|
|
960
|
+
const move = screenExtendedMove(row, column.id.slice(EXTENDED_COLUMN_PREFIX.length) as ExtendedSession);
|
|
961
|
+
return move == null
|
|
962
|
+
? { text: "--", color: colors.textDim }
|
|
963
|
+
: { text: formatPercentRaw(move), color: priceColor(move, colors) };
|
|
964
|
+
}
|
|
965
|
+
const field = column.id.startsWith("m:")
|
|
966
|
+
? (column.id.slice(2) as NumericField)
|
|
967
|
+
: null;
|
|
968
|
+
if (field) {
|
|
969
|
+
const observation = row.metrics[field];
|
|
970
|
+
const change = SIGN_COLORED.has(field) && observation.value != null
|
|
971
|
+
// A move that prints 0.0 stays neutral.
|
|
972
|
+
? priceColor(Math.abs(observation.value) < 0.05 ? 0 : observation.value, colors)
|
|
973
|
+
: undefined;
|
|
974
|
+
return {
|
|
975
|
+
text: formatScreenValue(field, observation.value),
|
|
976
|
+
color: observation.state === "stale" ? colors.warning : change,
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
const focus = row.metrics[metric];
|
|
980
|
+
if (column.id === "percentile")
|
|
981
|
+
return { text: rank(focus.percentile.value) };
|
|
982
|
+
if (column.id === "date")
|
|
983
|
+
return focus.state === "stale"
|
|
984
|
+
? { text: metricDate(focus).text, color: colors.warning }
|
|
985
|
+
: { text: "" };
|
|
986
|
+
if (column.id === "name") return { text: row.name ?? "--" };
|
|
987
|
+
return { text: String(row[column.id as "symbol"] ?? "--") };
|
|
988
|
+
}}
|
|
989
|
+
sortColumnId={
|
|
990
|
+
(NUMERIC_FIELDS as readonly string[]).includes(definition.sort.field)
|
|
991
|
+
? `m:${definition.sort.field}`
|
|
992
|
+
: definition.sort.field
|
|
970
993
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
sort: {
|
|
1001
|
-
field,
|
|
1002
|
-
direction:
|
|
1003
|
-
definition.sort.field === field &&
|
|
1004
|
-
definition.sort.direction === "desc"
|
|
1005
|
-
? "asc"
|
|
1006
|
-
: "desc",
|
|
1007
|
-
},
|
|
1008
|
-
});
|
|
1009
|
-
}}
|
|
1010
|
-
emptyStateTitle="No equities match this screen."
|
|
1011
|
-
/>
|
|
1012
|
-
</PaneStatusBody>
|
|
994
|
+
sortDirection={definition.sort.direction}
|
|
995
|
+
onHeaderClick={(id) => {
|
|
996
|
+
const field = id.startsWith("m:")
|
|
997
|
+
? (id.slice(2) as NumericField)
|
|
998
|
+
: id === "percentile" || id === "date"
|
|
999
|
+
? metric
|
|
1000
|
+
: ["symbol", "sector", "exchange"].includes(id)
|
|
1001
|
+
? (id as ScreenField)
|
|
1002
|
+
: null;
|
|
1003
|
+
if (!field) return;
|
|
1004
|
+
if ((NUMERIC_FIELDS as readonly string[]).includes(field))
|
|
1005
|
+
setMetric(field as NumericField);
|
|
1006
|
+
apply({
|
|
1007
|
+
...definition,
|
|
1008
|
+
sort: {
|
|
1009
|
+
field,
|
|
1010
|
+
direction:
|
|
1011
|
+
definition.sort.field === field &&
|
|
1012
|
+
definition.sort.direction === "desc"
|
|
1013
|
+
? "asc"
|
|
1014
|
+
: "desc",
|
|
1015
|
+
},
|
|
1016
|
+
});
|
|
1017
|
+
}}
|
|
1018
|
+
emptyStateTitle={currencyEmpty?.title ?? "No equities match this screen."}
|
|
1019
|
+
emptyStateHint={currencyEmpty ? `${currencyEmpty.detail} Choose ${currencyEmpty.base.currency} to screen in the base currency.` : undefined}
|
|
1020
|
+
/>
|
|
1021
|
+
</PaneStatusBody>
|
|
1022
|
+
</>
|
|
1013
1023
|
);
|
|
1014
1024
|
return (
|
|
1015
1025
|
<Box width={width} height={height} flexDirection="column">
|
|
@@ -45,10 +45,11 @@ export const estimateRevisionsHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
45
45
|
instrument?.exchange ?? "",
|
|
46
46
|
ctx.apiClient,
|
|
47
47
|
ctx.marketData,
|
|
48
|
-
).catch((error: unknown) => {
|
|
49
|
-
throw explainRefusal(error, estimateRevisionsLockNote, {
|
|
48
|
+
).catch(async (error: unknown) => {
|
|
49
|
+
throw await explainRefusal(error, estimateRevisionsLockNote, {
|
|
50
50
|
symbol: args.symbols[0] ?? null,
|
|
51
51
|
token: ctx.settings?.tab === "guidance" ? "GUID" : "EM",
|
|
52
|
+
context: ctx,
|
|
52
53
|
});
|
|
53
54
|
});
|
|
54
55
|
const periods = pinnedEstimatePeriods(
|
|
@@ -1,13 +1,37 @@
|
|
|
1
1
|
import type { PaneLock } from "../../../types/plugin";
|
|
2
|
-
import {
|
|
2
|
+
import { fetchCompanyData } from "../company-kpis/client";
|
|
3
|
+
import { filesForm } from "../shared/free-routes";
|
|
4
|
+
import { resolveHeadlessIssuerListing } from "../shared/headless-market-data";
|
|
5
|
+
import { proLockNote, type FreeRoute } from "../shared/pro-lock-note";
|
|
6
|
+
|
|
7
|
+
/** GUIDE's free preview, where it holds guidance for the company: Apple issues none, so GUIDE has no AAPL rows. */
|
|
8
|
+
const GUIDE_ROUTE: FreeRoute = {
|
|
9
|
+
text: (ticker, company) => `gloomberb fn GUIDE ${ticker} lists ${company}'s latest guidance ranges, with the date each was issued.`,
|
|
10
|
+
answers: async (symbol, context) => {
|
|
11
|
+
const data = await fetchCompanyData("guidance", symbol, {}, context.apiClient, await resolveHeadlessIssuerListing(context, symbol), context.signal);
|
|
12
|
+
return "guidance" in data && data.guidance.length + data.history.length > 0;
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** The company's 8-Ks, where results and any guidance it gives are filed, for a company that files them. */
|
|
17
|
+
const EIGHT_K_ROUTE: FreeRoute = {
|
|
18
|
+
text: (ticker, company) => `gloomberb filings ${ticker} --form 8-K lists ${company}'s 8-Ks, where it files its results and any guidance, each with its items and a link to the filing.`,
|
|
19
|
+
answers: (symbol, context) => filesForm(symbol, "8-K", context),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const EARNINGS_ROUTE: FreeRoute = {
|
|
23
|
+
text: (ticker, company) => `gloomberb earnings ${ticker} lists ${company}'s next report date with its consensus EPS estimate.`,
|
|
24
|
+
answers: async (symbol, context) => (await context.marketData.getEarningsCalendar?.([symbol]) ?? []).length > 0,
|
|
25
|
+
};
|
|
3
26
|
|
|
4
27
|
/**
|
|
5
28
|
* EM, EEO and GUID read one Gloom Cloud route that asks for a signed-in account
|
|
6
29
|
* with a verified email, and no plan. The report names the function it was run
|
|
7
|
-
* for. GUIDE lists a company's latest guidance ranges, as a free preview
|
|
8
|
-
* `earnings` lists the next report
|
|
30
|
+
* for. GUIDE lists a company's latest guidance ranges, as a free preview, and
|
|
31
|
+
* its 8-Ks hold what GUIDE does not cover; `earnings` lists the next report
|
|
32
|
+
* date with its consensus EPS estimate.
|
|
9
33
|
*/
|
|
10
|
-
export function estimateRevisionsLockNote(lock: PaneLock): string {
|
|
34
|
+
export function estimateRevisionsLockNote(lock: PaneLock): Promise<string> {
|
|
11
35
|
const guidance = lock.token === "GUID";
|
|
12
36
|
return proLockNote(lock, {
|
|
13
37
|
token: guidance ? "GUID" : "EM",
|
|
@@ -16,8 +40,6 @@ export function estimateRevisionsLockNote(lock: PaneLock): string {
|
|
|
16
40
|
? "it reads the EPS guidance a company issued, cited from its filings and calls, against consensus"
|
|
17
41
|
: "it reads consensus EPS estimates by fiscal period, how they were revised, the surprises against reported EPS and the guidance",
|
|
18
42
|
shows: "it",
|
|
19
|
-
free: guidance
|
|
20
|
-
? (ticker, company) => `gloomberb fn GUIDE ${ticker} lists ${company}'s latest guidance ranges, with the date each was issued.`
|
|
21
|
-
: (ticker, company) => `gloomberb earnings ${ticker} lists ${company}'s next report date with its consensus EPS estimate.`,
|
|
43
|
+
free: guidance ? [GUIDE_ROUTE, EIGHT_K_ROUTE] : [EARNINGS_ROUTE],
|
|
22
44
|
});
|
|
23
45
|
}
|
|
@@ -21,7 +21,10 @@ export function validateExposure(value: ExposurePayload): ExposurePayload {
|
|
|
21
21
|
&& typeof data.evaluatedAt === "string" && Number.isFinite(Date.parse(data.evaluatedAt))
|
|
22
22
|
&& Number.isInteger(data.requestedHoldings) && Number.isInteger(data.lockedHoldings) && data.lockedHoldings >= 0
|
|
23
23
|
&& Array.isArray(data.holdings) && strings(data.unknowns) && strings(data.methodology);
|
|
24
|
-
try { parseCustomScenario(JSON.stringify(data.scenario)); } catch {
|
|
24
|
+
try { parseCustomScenario(JSON.stringify(data.scenario)); } catch {
|
|
25
|
+
// Intentional ignore: invalid scenarios use the payload validation error below.
|
|
26
|
+
valid = false;
|
|
27
|
+
}
|
|
25
28
|
valid &&= data.holdings.every(h => typeof h.symbol === "string" && finite(h.weight) && Array.isArray(h.unknowns)
|
|
26
29
|
&& ["quantified", "partial", "unknown"].includes(h.status) && !!h.coverage
|
|
27
30
|
&& Array.isArray(h.measures) && h.measures.every(m => validRange(m.exposurePct) && nullableRange(m.impactPct) && strings(m.componentIds))
|
|
@@ -7,13 +7,13 @@ import type { ExposureScenario } from "../../../api-client/exposure";
|
|
|
7
7
|
import { parseCustomScenario } from "./model";
|
|
8
8
|
|
|
9
9
|
export interface ExposureInputs { input: string; source: string; nav: string; cash: string; depth: number; scenario: ExposureScenario; }
|
|
10
|
-
export function ExposureEditor({ initial, sources, focused, width, onSave, onCancel }: {
|
|
11
|
-
initial: ExposureInputs; sources: { value: string; label: string }[]; focused: boolean; width: number;
|
|
10
|
+
export function ExposureEditor({ initial, initialCustom, sources, focused, width, onSave, onCancel }: {
|
|
11
|
+
initial: ExposureInputs; initialCustom?: string; sources: { value: string; label: string }[]; focused: boolean; width: number;
|
|
12
12
|
onSave: (value: ExposureInputs) => void; onCancel: () => void;
|
|
13
13
|
}) {
|
|
14
14
|
const [draft, setDraft] = useState(initial);
|
|
15
15
|
const first = initial.scenario.shocks[0]!;
|
|
16
|
-
const [custom, setCustom] = useState(initial.scenario.shocks.length > 1 || !!first.transmission ? JSON.stringify(initial.scenario) : "");
|
|
16
|
+
const [custom, setCustom] = useState(initialCustom ?? (initial.scenario.shocks.length > 1 || !!first.transmission ? JSON.stringify(initial.scenario) : ""));
|
|
17
17
|
const [label, setLabel] = useState(initial.scenario.label);
|
|
18
18
|
const [kind, setKind] = useState(first.kind);
|
|
19
19
|
const [target, setTarget] = useState(first.target);
|
|
@@ -26,7 +26,10 @@ export const exposureScreenshotEvidence: PaneScreenshotEvidenceHook<ExposureCapt
|
|
|
26
26
|
const rows = metadata.tab === "drivers" ? driverRows(payload) : metadata.tab === "paths" ? pathRows(payload) : metadata.tab === "portfolio" ? portfolioRows(payload, String(metadata.view)) : tableRows(payload);
|
|
27
27
|
if (metadata.plottedValueCount !== rows.length || metadata.rowIds.length !== rows.length || metadata.rowIds.some(id => !rows.some(r => r.id === id))) return null;
|
|
28
28
|
return metadata as unknown as ExposureCapture;
|
|
29
|
-
} catch {
|
|
29
|
+
} catch {
|
|
30
|
+
// Intentional ignore: malformed capture metadata cannot prove the screenshot's exposure data.
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
30
33
|
},
|
|
31
34
|
mismatches(evidence, { resolved, payload }) {
|
|
32
35
|
const snapshot = payload.config.layout.instances.find(i => i.instanceId === payload.paneId)?.settings?.exposureSnapshot;
|
|
@@ -4,6 +4,7 @@ import type { Portfolio, TickerRecord } from "../../../types/ticker";
|
|
|
4
4
|
import { publicTickerKey } from "../../../utils/exchanges";
|
|
5
5
|
import { resolveCurrencyUnit } from "../../../utils/currency-units";
|
|
6
6
|
import { convertCurrency } from "../../../utils/format";
|
|
7
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
7
8
|
import { getPortfolioPositionMetrics, getPortfolioQuoteDisplay, resolvePortfolioMarketValue } from "../../../portfolio/position-metrics";
|
|
8
9
|
|
|
9
10
|
/** Explicit NAV preserves cash, shorts and leverage; net holdings are never used as NAV. */
|
|
@@ -35,7 +36,8 @@ export async function portfolioHoldings(portfolio: Portfolio, tickers: TickerRec
|
|
|
35
36
|
for (const currency of currencies) await ensureRate(currency);
|
|
36
37
|
const holdings: ExposureHolding[] = [];
|
|
37
38
|
for (const ticker of positions) {
|
|
38
|
-
const quote = await market.getQuote(ticker.metadata.ticker, ticker.metadata.exchange)
|
|
39
|
+
const quote = await market.getQuote(ticker.metadata.ticker, ticker.metadata.exchange)
|
|
40
|
+
.catch(logFailure("exposure.holdings", "Load portfolio holding quote failed", null));
|
|
39
41
|
const currency = quote?.currency || ticker.metadata.currency;
|
|
40
42
|
if (!rates.has(currency)) await ensureRate(currency);
|
|
41
43
|
const metrics = getPortfolioPositionMetrics(ticker, portfolio.id, currency, {
|