gloomberb 0.18.0 → 0.18.2
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/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 +6 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/types.ts +14 -0
- 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/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/fred-series.ts +15 -5
- package/src/cli/commands/market.ts +290 -159
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/overview.ts +31 -6
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +105 -52
- 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/helpers.ts +36 -3
- package/src/cli/index.ts +4 -1
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +37 -2
- package/src/cli/pane-functions/catalog.ts +3 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +9 -4
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +31 -7
- package/src/cli/pane-functions/headless.ts +6 -5
- package/src/cli/pane-functions/index.ts +3 -2
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +2 -1
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/series-search.ts +2 -1
- package/src/cli/symbol-list.ts +51 -16
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +22 -7
- package/src/components/chart/composite/format.ts +13 -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 +64 -16
- package/src/components/chart/composite/price-axis-labels.tsx +2 -1
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/command-bar/workflow/collection-actions.ts +4 -0
- package/src/components/index.ts +2 -0
- 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/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/onboarding/wizard-positions.ts +8 -0
- 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/status.tsx +17 -3
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- 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 +57 -22
- package/src/i18n/es.ts +9 -3
- package/src/i18n/ja.ts +9 -3
- package/src/i18n/ko.ts +9 -3
- package/src/i18n/zh-cn.ts +10 -4
- package/src/i18n/zh-tw.ts +10 -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/cli.ts +26 -18
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/analytics/index.tsx +1 -1
- package/src/plugins/builtin/analytics/risk-client.ts +44 -11
- package/src/plugins/builtin/analytics/risk-fx.ts +61 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +10 -1
- package/src/plugins/builtin/analytics/risk-model.ts +79 -16
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/analytics/risk-test-data.ts +3 -0
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- 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 +182 -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 +505 -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 +573 -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 +3 -2
- package/src/plugins/builtin/buildout/pane/status.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 +31 -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/cloud/team/pane-sections.tsx +10 -3
- package/src/plugins/builtin/cloud/team/pane.tsx +30 -9
- 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 +36 -5
- 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 +26 -12
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- 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/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/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/custom-view/loader.ts +3 -2
- package/src/plugins/builtin/custom-view/pane.tsx +5 -4
- package/src/plugins/builtin/custom-view/view-spec.ts +1 -0
- 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/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 +38 -2
- package/src/plugins/builtin/econ/fred-catalog.ts +6 -2
- package/src/plugins/builtin/econ/fred-series-map.ts +12 -1
- 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 +20 -5
- package/src/plugins/builtin/equity-screener/model.ts +40 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +24 -21
- 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/executives/data.ts +24 -8
- package/src/plugins/builtin/executives/index.tsx +2 -18
- package/src/plugins/builtin/executives/pane.tsx +34 -10
- 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 +37 -16
- 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/insider/base-values.ts +72 -0
- package/src/plugins/builtin/insider/headless.ts +29 -4
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- 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 +2 -2
- package/src/plugins/builtin/market-movers/index.tsx +0 -3
- package/src/plugins/builtin/market-movers/session-body.tsx +27 -11
- package/src/plugins/builtin/market-movers/session.ts +25 -10
- 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 +33 -13
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/letters/client.ts +1 -0
- package/src/plugins/builtin/news/wire/news/command-bar-search.ts +6 -3
- package/src/plugins/builtin/news/wire/news/detail-view.tsx +2 -1
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -1
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/feed-xml.ts +6 -1
- package/src/plugins/builtin/news/wire/rss/managed-feed.ts +1 -0
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/notes/cli.ts +33 -8
- package/src/plugins/builtin/notes/export.ts +49 -3
- package/src/plugins/builtin/notes/files.ts +14 -3
- package/src/plugins/builtin/notes/index.tsx +3 -3
- package/src/plugins/builtin/notes/quick-notes-pane.tsx +45 -10
- package/src/plugins/builtin/notes/store.ts +13 -3
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/view.tsx +77 -22
- package/src/plugins/builtin/options-calculator/pane.tsx +4 -2
- package/src/plugins/builtin/options-scenario/pane.tsx +41 -6
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -7
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +166 -15
- package/src/plugins/builtin/portfolio-list/cli/render.ts +69 -15
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +2 -2
- package/src/plugins/builtin/portfolio-list/headless.ts +3 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +44 -17
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -1
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +42 -7
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- 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/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/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/broker-reconnect.ts +105 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- 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-feature-wall.tsx +9 -4
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +62 -0
- 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/thirteenf/api.ts +4 -1
- package/src/plugins/builtin/thirteenf/data.ts +5 -1
- package/src/plugins/builtin/thirteenf/headless.ts +3 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +8 -3
- 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/pane.tsx +2 -2
- 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 +61 -0
- package/src/portfolio/mutations.ts +16 -2
- package/src/portfolio/position-metrics.ts +45 -4
- package/src/portfolio/position-quantity.ts +43 -0
- 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 +39 -7
- 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/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +6 -0
- package/src/renderers/dom/styles.css +12 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +12 -0
- 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/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +27 -2
- package/src/sources/gloomberb-cloud/index.ts +50 -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 +2 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +63 -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/team/collections.ts +2 -0
- package/src/tickers/listing-fate.ts +27 -8
- package/src/tickers/metadata.ts +10 -0
- 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 +5 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +12 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/ticker.ts +9 -0
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +2 -1
- package/src/utils/currency-units.ts +41 -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
|
@@ -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
|
}
|
|
@@ -13,8 +13,20 @@ import { secListingKey } from "../../../utils/sec";
|
|
|
13
13
|
* reads. A proxy is filed once a year and the extraction does not change
|
|
14
14
|
* after it is verified, so a statement is kept for a long time; the list
|
|
15
15
|
* of years is refreshed more often, since a new filing adds one.
|
|
16
|
+
*
|
|
17
|
+
* An account without Pro is served the preview (`access: "preview"`): each
|
|
18
|
+
* executive's total pay from the latest proxy. A kept answer is reused only
|
|
19
|
+
* for the plan it was served to, so a new Pro account never sees its old
|
|
20
|
+
* preview and an account that lost Pro never sees a kept breakdown.
|
|
16
21
|
*/
|
|
17
22
|
|
|
23
|
+
/** Pro's full statements, or the free preview. */
|
|
24
|
+
export type ProxyTier = "full" | "preview";
|
|
25
|
+
|
|
26
|
+
function tierOf(value: unknown): ProxyTier {
|
|
27
|
+
return (value as { access?: unknown } | null)?.access === "preview" ? "preview" : "full";
|
|
28
|
+
}
|
|
29
|
+
|
|
18
30
|
const LIST_KIND = "proxies";
|
|
19
31
|
const STATEMENT_KIND = "proxy";
|
|
20
32
|
const CACHE_SOURCE = "executives";
|
|
@@ -57,6 +69,7 @@ export function resetExecutivesPersistence(): void {
|
|
|
57
69
|
function loadCached<T>(
|
|
58
70
|
kind: string,
|
|
59
71
|
key: string,
|
|
72
|
+
tier: ProxyTier,
|
|
60
73
|
active: Map<string, ActiveRequest<T>>,
|
|
61
74
|
policy: { staleMs: number; expireMs: number },
|
|
62
75
|
force: boolean,
|
|
@@ -65,14 +78,15 @@ function loadCached<T>(
|
|
|
65
78
|
const store = persistence;
|
|
66
79
|
const options = { sourceKey: CACHE_SOURCE, schemaVersion: CACHE_SCHEMA_VERSION };
|
|
67
80
|
const failedKey = `${kind}:${key}`;
|
|
81
|
+
const activeKey = `${key}|${tier}`;
|
|
68
82
|
const hit = store?.getResource<T>(kind, key, options);
|
|
69
|
-
if (!force && !failedRefreshes.has(failedKey) && hit && !hit.stale) {
|
|
83
|
+
if (!force && !failedRefreshes.has(failedKey) && hit && !hit.stale && tierOf(hit.value) === tier) {
|
|
70
84
|
return Promise.resolve({ data: hit.value, fetchedAt: hit.fetchedAt });
|
|
71
85
|
}
|
|
72
|
-
const existing = active.get(
|
|
86
|
+
const existing = active.get(activeKey);
|
|
73
87
|
if (!force && existing?.store === store) return existing.promise;
|
|
74
88
|
const request: ActiveRequest<T> = { store, promise: null! };
|
|
75
|
-
const current = () => persistence === store && active.get(
|
|
89
|
+
const current = () => persistence === store && active.get(activeKey) === request;
|
|
76
90
|
request.promise = Promise.resolve().then(fetch).then((data) => {
|
|
77
91
|
const fetchedAt = Date.now();
|
|
78
92
|
if (current()) {
|
|
@@ -92,14 +106,14 @@ function loadCached<T>(
|
|
|
92
106
|
throw error;
|
|
93
107
|
}
|
|
94
108
|
const fallback = store?.getResource<T>(kind, key, { ...options, allowExpired: true });
|
|
95
|
-
if (!fallback) throw error;
|
|
109
|
+
if (!fallback || tierOf(fallback.value) !== tier) throw error;
|
|
96
110
|
return {
|
|
97
111
|
data: fallback.value,
|
|
98
112
|
fetchedAt: fallback.fetchedAt,
|
|
99
113
|
refreshError: (error instanceof Error ? error.message : String(error)).trim() || "Request failed",
|
|
100
114
|
};
|
|
101
|
-
}).finally(() => { if (active.get(
|
|
102
|
-
active.set(
|
|
115
|
+
}).finally(() => { if (active.get(activeKey) === request) active.delete(activeKey); });
|
|
116
|
+
active.set(activeKey, request);
|
|
103
117
|
return request.promise;
|
|
104
118
|
}
|
|
105
119
|
|
|
@@ -107,6 +121,8 @@ interface ProxyLoadOptions {
|
|
|
107
121
|
force?: boolean;
|
|
108
122
|
/** The listing's venue and company, for a listing outside the US. */
|
|
109
123
|
listing?: IssuerListingParams;
|
|
124
|
+
/** What the account's plan is served: `full` unless it has no Pro. */
|
|
125
|
+
tier?: ProxyTier;
|
|
110
126
|
}
|
|
111
127
|
|
|
112
128
|
export async function loadProxyStatements(
|
|
@@ -116,7 +132,7 @@ export async function loadProxyStatements(
|
|
|
116
132
|
const symbol = ticker.toUpperCase();
|
|
117
133
|
// SAN in Paris is not SAN in New York: a listing abroad is cached under its venue.
|
|
118
134
|
const key = secListingKey(symbol, options?.listing?.exchange);
|
|
119
|
-
return loadCached(LIST_KIND, key, activeListFetches, LIST_CACHE_POLICY, options?.force ?? false,
|
|
135
|
+
return loadCached(LIST_KIND, key, options?.tier ?? "full", activeListFetches, LIST_CACHE_POLICY, options?.force ?? false,
|
|
120
136
|
() => apiClient.getProxyStatements(symbol, options?.listing));
|
|
121
137
|
}
|
|
122
138
|
|
|
@@ -127,6 +143,6 @@ export async function loadProxyStatement(
|
|
|
127
143
|
): Promise<ProxyResult<CloudProxyStatementPayload>> {
|
|
128
144
|
const symbol = ticker.toUpperCase();
|
|
129
145
|
const key = `${secListingKey(symbol, options?.listing?.exchange)}:${year}`;
|
|
130
|
-
return loadCached(STATEMENT_KIND, key, activeStatementFetches, STATEMENT_CACHE_POLICY, options?.force ?? false,
|
|
146
|
+
return loadCached(STATEMENT_KIND, key, options?.tier ?? "full", activeStatementFetches, STATEMENT_CACHE_POLICY, options?.force ?? false,
|
|
131
147
|
() => apiClient.getProxyStatement(symbol, year, options?.listing));
|
|
132
148
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { PluginModule } from "../plugin-module";
|
|
2
|
-
import { proLockNote } from "../shared/pro-lock-note";
|
|
3
2
|
import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
|
|
4
3
|
import {
|
|
5
4
|
attachExecutivesPersistence,
|
|
@@ -11,21 +10,6 @@ import { isKnownNonUsListing } from "../../../utils/sec";
|
|
|
11
10
|
const description =
|
|
12
11
|
"Named executive officers and what they were paid, read from the company's proxy statement and checked against the filing.";
|
|
13
12
|
|
|
14
|
-
/**
|
|
15
|
-
* EXEC is Pro whole (`useProFeatureWall`). The proxy statements it reads are
|
|
16
|
-
* in the SEC filing list, which needs no account: `filings` lists them with a
|
|
17
|
-
* link to each, where the Summary Compensation Table is the pay as filed,
|
|
18
|
-
* without EXEC's parsed figures.
|
|
19
|
-
*/
|
|
20
|
-
function executivesLockNote(lock: { gate: "sign-in" | "pro"; symbol: string | null }): string {
|
|
21
|
-
return proLockNote(lock, {
|
|
22
|
-
token: "EXEC",
|
|
23
|
-
does: "it reads each proxy statement into the named executive officers and what they were paid",
|
|
24
|
-
shows: "it",
|
|
25
|
-
free: (ticker, company) => `gloomberb filings ${ticker} --form DEF14A lists ${company}'s proxy statements, each with its filing date and a link to the filing, whose Summary Compensation Table holds the pay as filed.`,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
13
|
export const executivesModule: PluginModule = {
|
|
30
14
|
setup(ctx) {
|
|
31
15
|
attachExecutivesPersistence(ctx.persistence);
|
|
@@ -47,8 +31,8 @@ export const executivesModule: PluginModule = {
|
|
|
47
31
|
{
|
|
48
32
|
id: EXECUTIVES_PANE_ID,
|
|
49
33
|
reportFreshness: { source: "SEC EDGAR", status: "not-a-feed", basis: "proxy statements" },
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
// Open on every plan: without Pro each executive's total pay from the
|
|
35
|
+
// latest proxy, with the breakdown and earlier years locked (pane.tsx).
|
|
52
36
|
name: "Executives",
|
|
53
37
|
icon: "X",
|
|
54
38
|
component: ExecutivesPane,
|
|
@@ -27,11 +27,14 @@ import {
|
|
|
27
27
|
} from "../../../ui";
|
|
28
28
|
import { isPermanentClientError } from "../../../api-client/errors";
|
|
29
29
|
import type { IssuerListingParams } from "../../../api-client/paths";
|
|
30
|
+
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
31
|
+
import { CLOUD_PLAN_KEY, useCloudUpgradeAction } from "../shared/cloud-upgrade";
|
|
30
32
|
import { useFilingYearReader } from "../shared/filing-year-reader";
|
|
33
|
+
import { UpgradeLabel } from "../shared/locked-rows";
|
|
31
34
|
import { useProFeatureWall, type ProFeatureWallCopy, type ReadGuard } from "../shared/pro-feature-wall";
|
|
32
35
|
import { useBoundTicker, useIssuerListing } from "../shared/ticker-request";
|
|
33
36
|
import { SplitBar } from "../../../components/ui/split-bar";
|
|
34
|
-
import { loadProxyStatement, loadProxyStatements } from "./data";
|
|
37
|
+
import { loadProxyStatement, loadProxyStatements, type ProxyTier } from "./data";
|
|
35
38
|
import {
|
|
36
39
|
equityShare,
|
|
37
40
|
formatChange,
|
|
@@ -127,9 +130,12 @@ function renderOfficerCell(row: CloudExecutiveRowPayload, column: OfficerColumn)
|
|
|
127
130
|
function ExecutiveRows({
|
|
128
131
|
rows,
|
|
129
132
|
width,
|
|
133
|
+
breakdown,
|
|
130
134
|
}: {
|
|
131
135
|
rows: CloudExecutiveRowPayload[];
|
|
132
136
|
width: number;
|
|
137
|
+
/** False on the free preview, whose rows carry the total alone: no EQ% column. */
|
|
138
|
+
breakdown: boolean;
|
|
133
139
|
}) {
|
|
134
140
|
const isDesktopWeb = useUiCapabilities().nativePaneChrome === true;
|
|
135
141
|
if (width < 52) {
|
|
@@ -146,7 +152,9 @@ function ExecutiveRows({
|
|
|
146
152
|
}
|
|
147
153
|
// The name column fits the longest name so the title starts after a kit gutter.
|
|
148
154
|
const nameWidth = Math.min(24, Math.max(10, ...rows.map((row) => row.name.length)));
|
|
149
|
-
const columns = OFFICER_COLUMNS
|
|
155
|
+
const columns = OFFICER_COLUMNS
|
|
156
|
+
.filter((column) => breakdown || column.id !== "equity")
|
|
157
|
+
.map((column) => column.id === "name" ? { ...column, width: nameWidth } : column);
|
|
150
158
|
const height = rows.length + 1;
|
|
151
159
|
return (
|
|
152
160
|
<Box flexDirection="column" height={isDesktopWeb ? undefined : height} flexShrink={0}>
|
|
@@ -251,22 +259,26 @@ export function ExecutivesPane({
|
|
|
251
259
|
/** Inside Ticker Research, whose own tab strip keeps h/l and the arrows. */
|
|
252
260
|
nested?: boolean;
|
|
253
261
|
}) {
|
|
254
|
-
|
|
262
|
+
// Every plan reads EXEC: without Pro the server serves each executive's
|
|
263
|
+
// total pay, so only its refusal (an older server, an expired session) walls it.
|
|
264
|
+
const { wall, guard } = useProFeatureWall(EXEC_WALL, { width, height }, { preview: true });
|
|
265
|
+
const tier: ProxyTier = usePlanAccess().hasProAccess ? "full" : "preview";
|
|
255
266
|
const { symbol, ticker: boundTicker } = useBoundTicker();
|
|
256
267
|
const ticker = symbol ? symbol.toUpperCase() : null;
|
|
257
268
|
const listing = useIssuerListing(boundTicker);
|
|
258
269
|
if (wall) return wall;
|
|
259
270
|
if (!ticker) return <EmptyState title="Select a ticker." />;
|
|
260
|
-
return <ExecutiveResearch key={ticker} ticker={ticker} listing={listing} focused={focused} width={width} nested={nested} guard={guard} />;
|
|
271
|
+
return <ExecutiveResearch key={ticker} ticker={ticker} listing={listing} tier={tier} focused={focused} width={width} nested={nested} guard={guard} />;
|
|
261
272
|
}
|
|
262
273
|
|
|
263
274
|
export function ExecutivesResearchTab(props: { focused: boolean; width: number; height: number }) {
|
|
264
275
|
return <ExecutivesPane {...props} nested />;
|
|
265
276
|
}
|
|
266
277
|
|
|
267
|
-
function ExecutiveResearch({ ticker, listing, focused, width, nested, guard }: {
|
|
278
|
+
function ExecutiveResearch({ ticker, listing, tier, focused, width, nested, guard }: {
|
|
268
279
|
ticker: string;
|
|
269
280
|
listing: IssuerListingParams;
|
|
281
|
+
tier: ProxyTier;
|
|
270
282
|
focused: boolean;
|
|
271
283
|
width: number;
|
|
272
284
|
nested: boolean;
|
|
@@ -274,15 +286,20 @@ function ExecutiveResearch({ ticker, listing, focused, width, nested, guard }: {
|
|
|
274
286
|
}) {
|
|
275
287
|
const nativePaneChrome = useUiCapabilities().nativePaneChrome === true;
|
|
276
288
|
const rendererHost = useRendererHost();
|
|
289
|
+
const openUpgrade = useCloudUpgradeAction("exec");
|
|
277
290
|
const [selectedYear, setYear] = usePaneStateValue<number | null>("proxyYear", null);
|
|
278
|
-
const loadYears = useCallback((force: boolean) => guard(loadProxyStatements(ticker, { force, listing })), [guard, listing, ticker]);
|
|
291
|
+
const loadYears = useCallback((force: boolean) => guard(loadProxyStatements(ticker, { force, listing, tier })), [guard, listing, ticker, tier]);
|
|
279
292
|
const list = useAsyncResource(loadYears, { clearOnError: isPermanentClientError });
|
|
280
293
|
const years = useMemo(() => (list.data?.data?.proxies ?? []).map((entry) => entry.proxyYear), [list.data]);
|
|
281
|
-
|
|
294
|
+
// Without Pro only the latest proxy is served: an earlier pick from a Pro
|
|
295
|
+
// session waits for Pro again rather than asking for a year that is refused.
|
|
296
|
+
const year = tier === "full" && selectedYear !== null && years.includes(selectedYear)
|
|
282
297
|
? selectedYear : years[0] ?? null;
|
|
283
|
-
const loadStatement = useCallback((force: boolean) => guard(loadProxyStatement(ticker, year!, { force, listing })), [guard, listing, ticker, year]);
|
|
298
|
+
const loadStatement = useCallback((force: boolean) => guard(loadProxyStatement(ticker, year!, { force, listing, tier })), [guard, listing, ticker, year, tier]);
|
|
284
299
|
const detail = useAsyncResource(year === null ? null : loadStatement, { clearOnError: isPermanentClientError });
|
|
285
300
|
const statement = detail.data?.data ?? null;
|
|
301
|
+
// The free preview: each executive's total pay; the breakdown and earlier years are Pro.
|
|
302
|
+
const preview = statement?.access === "preview" || list.data?.data?.access === "preview";
|
|
286
303
|
const loading = list.loading || detail.loading;
|
|
287
304
|
const listError = list.error ?? list.data?.refreshError;
|
|
288
305
|
const statementError = detail.error ?? detail.data?.refreshError;
|
|
@@ -332,8 +349,9 @@ function ExecutiveResearch({ ticker, listing, focused, width, nested, guard }: {
|
|
|
332
349
|
// `r` refreshes every pane, so it gets no hint here.
|
|
333
350
|
const hints: PaneHint[] = statement ? [{ id: "open", key: "o", label: "pen filing", onPress: openFiling }] : [];
|
|
334
351
|
if (yearHint) hints.push(yearHint);
|
|
352
|
+
if (preview) hints.push({ id: "upgrade", key: CLOUD_PLAN_KEY, label: "upgrade", title: "Upgrade to Pro", onPress: openUpgrade });
|
|
335
353
|
return { info, hints };
|
|
336
|
-
}, [loading, statement, openFiling, yearHint]);
|
|
354
|
+
}, [loading, statement, openFiling, yearHint, preview, openUpgrade]);
|
|
337
355
|
|
|
338
356
|
const figures = useMemo(
|
|
339
357
|
() => (statement ? figuresOf(statement) : []),
|
|
@@ -390,7 +408,7 @@ function ExecutiveResearch({ ticker, listing, focused, width, nested, guard }: {
|
|
|
390
408
|
<FigureList figures={figures} width={proseWidth} minValueWidth={4} maxValueWidth={14} />
|
|
391
409
|
</Box>
|
|
392
410
|
)}
|
|
393
|
-
{statement.ceo && (
|
|
411
|
+
{statement.ceo && !preview && (
|
|
394
412
|
<Box flexDirection="column">
|
|
395
413
|
<SectionHeading marginTop={figures.length > 0 ? 1 : 0}
|
|
396
414
|
title={`How ${statement.ceo.name.trim().split(/\s+/).pop() || "the CEO"} Was Paid`}
|
|
@@ -404,9 +422,15 @@ function ExecutiveResearch({ ticker, listing, focused, width, nested, guard }: {
|
|
|
404
422
|
<ExecutiveRows
|
|
405
423
|
rows={statement.namedExecutives}
|
|
406
424
|
width={proseWidth}
|
|
425
|
+
breakdown={!preview}
|
|
407
426
|
/>
|
|
408
427
|
</Box>
|
|
409
428
|
)}
|
|
429
|
+
{preview && (
|
|
430
|
+
<Box marginTop={1}>
|
|
431
|
+
<UpgradeLabel text="Upgrade for the pay breakdown and earlier years" onPress={openUpgrade} role="exec-upgrade" />
|
|
432
|
+
</Box>
|
|
433
|
+
)}
|
|
410
434
|
{statement.highlights && (
|
|
411
435
|
<Box flexDirection="column">
|
|
412
436
|
<SectionHeading marginTop={1} title="What Changed" />
|
|
@@ -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, {
|
|
@@ -2,12 +2,13 @@ import { useCallback, useMemo, useRef } from "react";
|
|
|
2
2
|
import { isAccessDenied } from "../../../api-client/errors";
|
|
3
3
|
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
4
4
|
import type { ExposureEvidence, ExposurePayload } from "../../../api-client/exposure";
|
|
5
|
-
import { ActionRow, ChartTableHeader, DataTableView, EmptyState, KeyValueRow, PageStackView, PaneStatusBody, QueryBar, SectionHeading, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, type DataTableCell, type DataTableColumn, type PaneHint, type StatItem } from "../../../components";
|
|
5
|
+
import { ActionRow, Button, ChartTableHeader, DataTableView, EmptyState, KeyValueRow, PageStackView, PaneStatusBody, QueryBar, SectionHeading, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, type DataTableCell, type DataTableColumn, type PaneHint, type StatItem } from "../../../components";
|
|
6
6
|
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
7
7
|
import { useAppSelector, useAsyncResource, useAutoRefresh, useMarketData, usePaneInstance, usePaneSettingValue, usePluginAppActions, usePluginPaneState } from "../../../public/react";
|
|
8
8
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
9
9
|
import type { PaneProps } from "../../../types/plugin";
|
|
10
10
|
import { Box, ScrollBox, Text, useRendererHost, useUiCapabilities } from "../../../ui";
|
|
11
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
11
12
|
import { SignInWall } from "../cloud/auth-actions";
|
|
12
13
|
import { CLOUD_PLAN_KEY, useCloudUpgradeAction } from "../shared/cloud-upgrade";
|
|
13
14
|
import { useOpenDescription } from "../shared/open-description";
|
|
@@ -82,14 +83,32 @@ export function ExposurePane({ width, height, focused }: PaneProps) {
|
|
|
82
83
|
const { createPaneFromTemplate } = usePluginAppActions();
|
|
83
84
|
const openDescription = useOpenDescription();
|
|
84
85
|
const [openingScenario] = usePaneSettingValue("scenario", "taiwan-disruption");
|
|
85
|
-
const [customSetting] = usePaneSettingValue("customScenario", "");
|
|
86
|
+
const [customSetting, setCustomSetting] = usePaneSettingValue("customScenario", "");
|
|
86
87
|
const [openingDepth] = usePaneSettingValue("depth", 2);
|
|
87
88
|
const [openingNav] = usePaneSettingValue("nav", "");
|
|
88
89
|
const [openingCash] = usePaneSettingValue("cash", "");
|
|
89
|
-
const [snapshotSetting] = usePaneSettingValue<ExposurePayload | null>("exposureSnapshot", null);
|
|
90
|
-
const
|
|
90
|
+
const [snapshotSetting, setSnapshotSetting] = usePaneSettingValue<ExposurePayload | null>("exposureSnapshot", null);
|
|
91
|
+
const savedSnapshot = useMemo(() => {
|
|
92
|
+
try { return { data: snapshotSetting === null ? null : validateExposure(snapshotSetting), error: null }; } catch (error) {
|
|
93
|
+
return logFailure("exposure.pane", "Read saved exposure snapshot failed", {
|
|
94
|
+
data: null, error: "Cannot read the saved exposure snapshot. Repair or reset it before analyzing.",
|
|
95
|
+
})(error);
|
|
96
|
+
}
|
|
97
|
+
}, [snapshotSetting]);
|
|
98
|
+
const snapshot = savedSnapshot.data;
|
|
91
99
|
const initialInput = snapshot ? snapshot.holdings.map(h => `${h.symbol}=${h.weight}`).join(" ") : instance?.params?.input ?? instance?.params?.arg ?? "";
|
|
92
|
-
const
|
|
100
|
+
const savedScenario = useMemo(() => {
|
|
101
|
+
try { return { data: snapshot?.scenario ?? (customSetting ? parseCustomScenario(customSetting) : DEFAULT_SCENARIO), error: null }; } catch (error) {
|
|
102
|
+
return logFailure("exposure.pane", "Read custom exposure scenario failed", {
|
|
103
|
+
data: null, error: "Cannot read the custom scenario. Repair or reset it before analyzing.",
|
|
104
|
+
})(error);
|
|
105
|
+
}
|
|
106
|
+
}, [snapshot, customSetting]);
|
|
107
|
+
const inputError = savedSnapshot.error ?? savedScenario.error;
|
|
108
|
+
// A repaired setting must replace any stale scenario in persisted editor state.
|
|
109
|
+
const repairingCustomSetting = useRef(false);
|
|
110
|
+
if (savedScenario.error) repairingCustomSetting.current = true;
|
|
111
|
+
const initialScenario = savedScenario.data ?? DEFAULT_SCENARIO;
|
|
93
112
|
const [inputs, setInputs] = usePluginPaneState<ExposureInputs>("exposure:inputs", { input: initialInput, source: /^(PORT|WATCH):/.test(initialInput) ? initialInput : "typed", nav: String(openingNav), cash: String(openingCash), depth: snapshot?.depth ?? Number(openingDepth), scenario: initialScenario });
|
|
94
113
|
const [scenarioId, setScenarioId] = usePluginPaneState("exposure:scenario", snapshot ? snapshot.scenario.id ?? "captured" : customSetting ? "custom" : openingScenario);
|
|
95
114
|
const [openingTab] = usePaneSettingValue("tab", "table");
|
|
@@ -98,11 +117,11 @@ export function ExposurePane({ width, height, focused }: PaneProps) {
|
|
|
98
117
|
const [view, setView] = usePluginPaneState("exposure:view", openingView);
|
|
99
118
|
const [selectedId, setSelected] = usePluginPaneState<string | null>("exposure:selected", null);
|
|
100
119
|
const [detailId, setDetail] = usePluginPaneState<string | null>("exposure:detail", null);
|
|
101
|
-
const [editing, setEditing] = usePluginPaneState("exposure:editing", !initialInput && !snapshot);
|
|
120
|
+
const [editing, setEditing] = usePluginPaneState("exposure:editing", !initialInput && !snapshot && !inputError);
|
|
102
121
|
const [sort, setSort] = usePluginPaneState("exposure:sort", { column: "exposure", direction: "desc" as "asc" | "desc" });
|
|
103
122
|
const libraryLoader = useCallback(() => snapshot ? Promise.resolve([snapshot.scenario]) : fetchScenarios(), [snapshot, accessKey]);
|
|
104
|
-
const library = useAsyncResource(libraryLoader, { clearOnError: isAccessDenied });
|
|
105
|
-
const scenario = snapshot?.scenario ?? (scenarioId === "custom" ? inputs.scenario : library.data?.find(s => s.id === scenarioId) ?? (scenarioId === DEFAULT_SCENARIO.id ? DEFAULT_SCENARIO : null));
|
|
123
|
+
const library = useAsyncResource(inputError ? null : libraryLoader, { clearOnError: isAccessDenied });
|
|
124
|
+
const scenario = snapshot?.scenario ?? (scenarioId === "custom" ? repairingCustomSetting.current && customSetting ? savedScenario.data : inputs.scenario : library.data?.find(s => s.id === scenarioId) ?? (scenarioId === DEFAULT_SCENARIO.id ? DEFAULT_SCENARIO : null));
|
|
106
125
|
const portfolio = config.portfolios.find(p => inputs.source === `PORT:${p.id}`);
|
|
107
126
|
const localTickers = useMemo(() => [...tickers.values()], [tickers]);
|
|
108
127
|
// Freeze a local selection until the user explicitly refreshes. Tick-by-tick marks must not rerun scenarios.
|
|
@@ -126,10 +145,10 @@ export function ExposurePane({ width, height, focused }: PaneProps) {
|
|
|
126
145
|
return fetchExposure({ holdings, scenario, depth: inputs.depth, ...(cash === undefined ? {} : { cashWeight: cash }) });
|
|
127
146
|
// Explicit request state, including the account, owns recalculation. Quote changes do not.
|
|
128
147
|
}, [sourceKey, snapshot]);
|
|
129
|
-
const resource = useAsyncResource(inputs.input || inputs.source !== "typed" || snapshot ? loader : null, { clearOnError: isAccessDenied });
|
|
148
|
+
const resource = useAsyncResource(!inputError && (inputs.input || inputs.source !== "typed" || snapshot) ? loader : null, { clearOnError: isAccessDenied });
|
|
130
149
|
const data = resource.data;
|
|
131
150
|
useAutoRefresh(resource.updatedAt, resource.load);
|
|
132
|
-
usePaneRefreshKey(() => void resource.reload(), { focused: focused && !editing });
|
|
151
|
+
usePaneRefreshKey(() => void resource.reload(), { focused: focused && !editing && !inputError });
|
|
133
152
|
const rawRows = useMemo(() => !data ? [] : tab === "drivers" ? driverRows(data) : tab === "paths" ? pathRows(data) : tab === "portfolio" ? portfolioRows(data, view) : tableRows(data), [data, tab, view]);
|
|
134
153
|
const rows = useMemo(() => [...rawRows].sort((a, b) => {
|
|
135
154
|
const get = (r: ExposureRow) => sort.column === "value" ? r.driver?.value ?? r.driver?.range?.high ?? -Infinity : sort.column === "units" ? r.driver ? driverUnits(r.driver) : "" : sort.column === "scope" ? r.driver ? driverScope(r.driver) : "" : sort.column === "classification" ? r.classification : sort.column === "label" ? r.label : sort.column === "period" ? r.period ?? "" : sort.column === "exposure" ? r.exposure?.high ?? -Infinity : sort.column === "impact" ? r.impact?.low ?? -Infinity : sort.column === "weight" ? r.weight ?? -Infinity : sort.column === "basis" ? r.basis ?? "" : r.symbol;
|
|
@@ -141,12 +160,12 @@ export function ExposurePane({ width, height, focused }: PaneProps) {
|
|
|
141
160
|
const activeRow = detail ?? selected;
|
|
142
161
|
const source = activeRow?.components.flatMap(c => [...c.evidence, ...c.path.flatMap(h => h.evidence)]).find(e => e.url)?.url;
|
|
143
162
|
const symbol = activeRow?.symbol.split(",")[0]?.trim();
|
|
144
|
-
const detailOpen = editing || !!detail || detailId === "unknowns";
|
|
163
|
+
const detailOpen = !savedSnapshot.error && (editing || (!inputError && (!!detail || detailId === "unknowns")));
|
|
145
164
|
const { strip, rows: tabRows } = usePaneTabs({ tabs: TABS, activeValue: tab, onSelect: value => { setTab(value); setDetail(null); }, focused: focused && !editing, dense: true });
|
|
146
165
|
useExposureEvidence(data, tab, view, rows.map(r => r.id));
|
|
147
|
-
const hints: PaneHint[] = editing ? [] : [
|
|
166
|
+
const hints: PaneHint[] = editing || savedSnapshot.error ? [] : [
|
|
148
167
|
{ id: "edit", key: "s", label: "cenario", title: "Edit scenario and holdings", onPress: () => setEditing(true) },
|
|
149
|
-
{ id: "unknowns", key: "v", label: "isibility", title: "What we could not see", onPress: () => setDetail("unknowns") },
|
|
168
|
+
...(!inputError ? [{ id: "unknowns", key: "v", label: "isibility", title: "What we could not see", onPress: () => setDetail("unknowns") }] : []),
|
|
150
169
|
...(selected ? [{ id: "evidence", key: "e", label: "vidence", onPress: () => setDetail(selected.id) }] : []),
|
|
151
170
|
...(source ? [{ id: "source", key: "o", label: "pen source", onPress: () => void host.openExternal(source) }] : []),
|
|
152
171
|
...(symbol ? [
|
|
@@ -158,7 +177,7 @@ export function ExposurePane({ width, height, focused }: PaneProps) {
|
|
|
158
177
|
] : []),
|
|
159
178
|
...(data?.access === "preview" ? [{ id: "upgrade", key: CLOUD_PLAN_KEY, label: "upgrade", title: "Upgrade to Pro", onPress: upgrade }] : []),
|
|
160
179
|
];
|
|
161
|
-
usePaneStatusFooter({ registrationId: "exposure", loading: resource.loading, error: data ? resource.error : null, hints,
|
|
180
|
+
usePaneStatusFooter({ registrationId: "exposure", loading: resource.loading, error: inputError ?? (data ? resource.error : null), hints,
|
|
162
181
|
info: data ? [{ id: "asof", parts: [{ text: `analyzed ${data.evaluatedAt.slice(0, 10)}`, tone: "muted" as const }] }] : [] });
|
|
163
182
|
usePaneNoticeFooter({ registrationId: "exposure:notices", focused, notices: [...(library.error ? [library.error] : []), ...(data?.unknowns ?? []), ...(data?.holdings.flatMap(h => h.unknowns.map(u => `${h.symbol}: ${u}`)) ?? [])] });
|
|
164
183
|
const sources = [{ value: "typed", label: "Typed holdings" }, ...config.portfolios.map(p => ({ value: `PORT:${p.id}`, label: `PORT · ${p.name}` })), ...config.watchlists.map(w => ({ value: `WATCH:${w.id}`, label: `Watchlist · ${w.name}` }))];
|
|
@@ -231,8 +250,10 @@ export function ExposurePane({ width, height, focused }: PaneProps) {
|
|
|
231
250
|
{strip}
|
|
232
251
|
<PageStackView focused={focused && !editing} detailOpen={detailOpen} onBack={() => { setDetail(null); setEditing(false); }}
|
|
233
252
|
detailTitle={editing ? "Scenario and holdings" : detail ? `${detail.symbol} · ${detail.label}` : "What we could not see"}
|
|
234
|
-
detailContent={editing ? <ExposureEditor initial={{ ...inputs, scenario: scenario ?? inputs.scenario }} sources={sources} focused={focused} width={width} onCancel={() => setEditing(false)} onSave={value => { setInputs(value); setScenarioId("custom"); setEditing(false); setDetail(null); }} /> : data ? <ExposureDetail row={detail} data={data} width={width} /> : null}
|
|
235
|
-
rootContent={<PaneStatusBody loading={!data && resource.loading} error={!data ? resource.error : null} subject="exposure analysis"
|
|
253
|
+
detailContent={editing && !savedSnapshot.error ? <ExposureEditor initial={{ ...inputs, scenario: scenario ?? inputs.scenario }} initialCustom={savedScenario.error ? customSetting : undefined} sources={sources} focused={focused} width={width} onCancel={() => setEditing(false)} onSave={value => { if (repairingCustomSetting.current && customSetting) setCustomSetting(JSON.stringify(value.scenario)); setInputs(value); setScenarioId("custom"); setEditing(false); setDetail(null); }} /> : data ? <ExposureDetail row={detail} data={data} width={width} /> : null}
|
|
254
|
+
rootContent={<PaneStatusBody loading={!data && resource.loading} error={inputError ?? (!data ? resource.error : null)} subject="exposure analysis"
|
|
255
|
+
actions={savedSnapshot.error ? <Button label="Reset saved snapshot" onPress={() => { setSnapshotSetting(null); setEditing(false); setDetail(null); }} />
|
|
256
|
+
: savedScenario.error ? <><Button label="Edit scenario" onPress={() => setEditing(true)} /><Button label="Reset custom scenario" onPress={() => { setCustomSetting(""); setInputs({ ...inputs, scenario: DEFAULT_SCENARIO }); setScenarioId(DEFAULT_SCENARIO.id!); setEditing(false); setDetail(null); }} /></> : undefined}>
|
|
236
257
|
{data ? <DataTableView<ExposureRow> columns={columns} items={items} getItemKey={r => r.id} rootWidth={width} rootHeight={bodyHeight} focused={focused && !detailOpen}
|
|
237
258
|
selection={{ kind: "id", selectedId: selected?.id ?? null, getId: r => r.id, onChange: (id) => { if (!id.startsWith("locked:")) setSelected(id); } }}
|
|
238
259
|
onActivate={row => row.id.startsWith("locked:") ? upgrade() : setDetail(row.id)} selectedTextOverridesCellColor showHorizontalScrollbar
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { PluginModule } from "../plugin-module";
|
|
2
|
+
import type { PaneLock } from "../../../types/plugin";
|
|
3
|
+
import { filesForm } from "../shared/free-routes";
|
|
2
4
|
import { proLockNote } from "../shared/pro-lock-note";
|
|
3
5
|
import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
|
|
4
6
|
import { FILING_EVENTS_PANE_ID, FilingEventsPane } from "./pane";
|
|
@@ -11,14 +13,18 @@ const description =
|
|
|
11
13
|
* EK is Pro whole (`useProFeatureWall`): signed out it shows the sign-in wall
|
|
12
14
|
* and on a free account the Pro wall, never a partial feed. The 8-Ks themselves
|
|
13
15
|
* are in SEC, which needs no account: its rows carry each 8-K's filing date,
|
|
14
|
-
* acceptance time and items, by number and in words.
|
|
16
|
+
* acceptance time and items, by number and in words. A company filing no 8-Ks
|
|
17
|
+
* (ASML files 6-Ks) gets no route.
|
|
15
18
|
*/
|
|
16
|
-
function filingEventsLockNote(lock:
|
|
19
|
+
function filingEventsLockNote(lock: PaneLock): Promise<string> {
|
|
17
20
|
return proLockNote(lock, {
|
|
18
21
|
token: "EK",
|
|
19
22
|
does: "it reads each 8-K and sums up what it said and who it names",
|
|
20
23
|
shows: "them",
|
|
21
|
-
free:
|
|
24
|
+
free: [{
|
|
25
|
+
text: (ticker, company) => `gloomberb fn SEC ${ticker} lists ${company}'s recent filings, 8-Ks among them, each with its filing date and its items by number and name.`,
|
|
26
|
+
answers: (symbol, context) => filesForm(symbol, "8-K", context),
|
|
27
|
+
}],
|
|
22
28
|
});
|
|
23
29
|
}
|
|
24
30
|
|
|
@@ -3,6 +3,7 @@ import { colors, priceColor } from "../../../theme/colors";
|
|
|
3
3
|
import type { Quote } from "../../../types/financials";
|
|
4
4
|
import { TextAttributes } from "../../../ui";
|
|
5
5
|
import { formatCompact, formatNumber, formatPercentRaw } from "../../../utils/format";
|
|
6
|
+
import { minorUnitSign } from "../../../utils/currency-units";
|
|
6
7
|
import { isBoardRowLoading, renderQuoteBoardCell, type BoardQuoteMap } from "../shared/use-quote-board";
|
|
7
8
|
import { tickDecimals, type FuturesContract } from "../../../market-data/futures-board";
|
|
8
9
|
import { FUTURES_RETURN_COLUMNS, futuresContractHeadline, futuresUnitLabel, type FuturesColumnId, type FuturesTableRow } from "./model";
|
|
@@ -164,13 +165,15 @@ const currencySymbols = new Map<string, string>();
|
|
|
164
165
|
/**
|
|
165
166
|
* The board prints dollar prices bare. Anything else carries its unit on the
|
|
166
167
|
* price and on the change, so Dutch TTF gas at 72.071 euros is not read as
|
|
167
|
-
* dollars beside Henry Hub:
|
|
168
|
-
* currencies as their
|
|
168
|
+
* dollars beside Henry Hub: a minor unit as its sign after the amount (US
|
|
169
|
+
* cents 312.35¢, as every price in cents reads), other currencies as their
|
|
170
|
+
* symbol.
|
|
169
171
|
*/
|
|
170
172
|
function currencyMarks(currency: string | undefined): { prefix: string; suffix: string } {
|
|
171
173
|
const code = currency?.trim().toUpperCase() ?? "";
|
|
172
174
|
if (!code || code === "USD") return { prefix: "", suffix: "" };
|
|
173
|
-
|
|
175
|
+
const minor = minorUnitSign(currency);
|
|
176
|
+
if (minor) return { prefix: "", suffix: minor };
|
|
174
177
|
let symbol = currencySymbols.get(code);
|
|
175
178
|
if (symbol === undefined) {
|
|
176
179
|
try {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { chartTableChromeRows, ChartTableHeader, CurveSurface, curveGhostColors, DataTableView, Notice, PaneStatusBody, QueryBar, useChartTableSelection, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, type DataTableColumn, type PaneFooterSegment, type StatItem } from "../../../components";
|
|
2
|
+
import { splitDelayedStatus, chartTableChromeRows, ChartTableHeader, CurveSurface, curveGhostColors, DataTableView, Notice, PaneStatusBody, QueryBar, useChartTableSelection, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, type DataTableColumn, type PaneFooterSegment, type StatItem } from "../../../components";
|
|
3
3
|
import { fitChartTableColumns } from "../../../components/chart-table";
|
|
4
4
|
import { curveStrip, curveSurfaceMinRows } from "../../../components/chart/curve";
|
|
5
5
|
import { isAccessDenied } from "../../../api-client/errors";
|
|
@@ -225,7 +225,7 @@ function FuturesCurveView({ width, height, focused, root, ownsTitle }: PaneProps
|
|
|
225
225
|
...requestedDate ? [{ id: "latest", key: "c", label: "urrent", title: "Current Curve", onPress: () => selectDate("") }] : [],
|
|
226
226
|
],
|
|
227
227
|
info: data ? [
|
|
228
|
-
{ id: "source", parts: [{ text: `${requestedDate ? "daily archive" : data.source === "cboe" ? "settlement" : delay > 0 ? `${delay}m delayed` : "dated quotes"} · ${unit ?? "units unavailable"} · ${curveTimestamp(newest)}${newest?.includes("T") ? " UTC" : ""}`, tone: "muted" }] },
|
|
228
|
+
...splitDelayedStatus({ id: "source", parts: [{ text: `${requestedDate ? "daily archive" : data.source === "cboe" ? "settlement" : delay > 0 ? `${delay}m delayed` : "dated quotes"} · ${unit ?? "units unavailable"} · ${curveTimestamp(newest)}${newest?.includes("T") ? " UTC" : ""}`, tone: "muted" }] }),
|
|
229
229
|
...(staleCount ? [{ id: "stale", parts: [{ text: `${staleCount} of ${data.contracts.length} stale`, tone: "warning" as const }] }] : []),
|
|
230
230
|
...(spot ? [spot.status === "ok" ? spotSegment(spot, root, clock)
|
|
231
231
|
: { id: "spot", parts: [{ text: `Basis blank: ${spot.reason}`, tone: "warning" as const }] }] : []),
|