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
|
@@ -5,7 +5,7 @@ import type { PricePoint } from "../../../types/financials";
|
|
|
5
5
|
import { formatPriceObservation } from "../../../market-data/market/format";
|
|
6
6
|
import { cleanFloat32Price } from "../../../cli/history-rows";
|
|
7
7
|
import { cryptoPairCoin, isFuturesSymbol } from "../shared/crypto-pair";
|
|
8
|
-
import { FUTURES_CONTRACTS, futuresBoardContract, futuresBoardContractForTicker, type FuturesContract } from "../../../market-data/futures-board";
|
|
8
|
+
import { bestNameMatch, FUTURES_CONTRACTS, futuresBoardContract, futuresBoardContractForTicker, futuresContractByName, type FuturesContract } from "../../../market-data/futures-board";
|
|
9
9
|
|
|
10
10
|
export const COT_CLASSES: Record<CotFamily, Array<{ value: CotClass; label: string }>> = {
|
|
11
11
|
legacy: [{ value: "noncommercial", label: "Noncommercial" }, { value: "commercial", label: "Commercial" }, { value: "nonreportable", label: "Nonreportable" }],
|
|
@@ -124,25 +124,48 @@ export function cotScope(value: unknown): CotScope { return value === "all" ? "a
|
|
|
124
124
|
const rootAlias = (input: string) => ROOTS[input === "VIX" ? "VX" : input]?.code ?? null;
|
|
125
125
|
const marketCode = (input: string) => /^[0-9A-Z]{5}[0-9A-Z+]$/.test(input) ? input : null;
|
|
126
126
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
127
|
+
* The FUT board row a typed argument names, by code, symbol or commodity name
|
|
128
|
+
* as FUT reads them (CC, CC=F, cocoa), or a verified root named the same way
|
|
129
|
+
* (bitcoin, VIX, SOFR, fed funds).
|
|
130
|
+
*/
|
|
131
|
+
function typedMarket(value: string): { board: FuturesContract } | { root: string } | null {
|
|
132
|
+
const board = futuresBoardContract(value) ?? futuresContractByName(value);
|
|
133
|
+
if (board) return { board };
|
|
134
|
+
const root = bestNameMatch(Object.keys(ROOTS), (key) => [ROOTS[key]!.name], value);
|
|
135
|
+
return root ? { root } : null;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* A CFTC code, root or commodity name typed as an argument or stored in a
|
|
139
|
+
* pane: every alias, crypto included, every FUT board root the CFTC reports,
|
|
140
|
+
* and the names FUT finds them by (cocoa, coffee, sugar, gold, crude). A typed
|
|
141
|
+
* root is always the future, so ZM here is soybean meal, never Zoom. A CFTC
|
|
142
|
+
* code is tried after the names, as one could be spelled with six letters
|
|
143
|
+
* (COFFEE, COPPER).
|
|
130
144
|
*/
|
|
131
145
|
export function cotContractCode(value: unknown): string | null {
|
|
132
146
|
if (typeof value !== "string") return null;
|
|
133
147
|
const input = value.trim().toUpperCase().replace(/=F$/, "");
|
|
134
|
-
const
|
|
135
|
-
|
|
148
|
+
const alias = rootAlias(input);
|
|
149
|
+
if (alias) return alias;
|
|
150
|
+
const market = typedMarket(value);
|
|
151
|
+
if (market && "root" in market) return ROOTS[market.root]!.code;
|
|
152
|
+
return (market ? boardMarketCode(market.board) : null) ?? (market ? null : marketCode(input));
|
|
136
153
|
}
|
|
154
|
+
/** The markets the unknown-market hint names, by the code and the word a newcomer knows them by. */
|
|
155
|
+
const SUGGESTED_MARKETS: ReadonlyArray<readonly [string, string]> = [
|
|
156
|
+
["ES", "S&P 500"], ["CL", "crude oil"], ["NG", "natural gas"], ["GC", "gold"], ["HG", "copper"],
|
|
157
|
+
["ZC", "corn"], ["CC", "cocoa"], ["KC", "coffee"], ["SB", "sugar"], ["ZN", "10-year note"],
|
|
158
|
+
];
|
|
137
159
|
/**
|
|
138
|
-
* What a market `cotContractCode` does not know is told: a FUT board
|
|
139
|
-
* CFTC does not report says so; anything else gets
|
|
160
|
+
* What a market `cotContractCode` does not know is told: a FUT board row the
|
|
161
|
+
* CFTC does not report says so; anything else gets codes and names that work,
|
|
162
|
+
* softs and metals among them, and a CFTC code.
|
|
140
163
|
*/
|
|
141
164
|
export function cotUnknownMarketMessage(value: unknown): string {
|
|
142
|
-
const
|
|
143
|
-
if (board) return `${board.code} (${board.name}) is not reported in the CFTC COT.`;
|
|
144
|
-
const
|
|
145
|
-
return `Unknown market "${String(value)}". Try ${
|
|
165
|
+
const market = typeof value === "string" ? typedMarket(value) : null;
|
|
166
|
+
if (market && "board" in market) return `${market.board.code} (${market.board.name}) is not reported in the CFTC COT.`;
|
|
167
|
+
const known = SUGGESTED_MARKETS.map(([code, name]) => `${code} ${name}`).join(", ");
|
|
168
|
+
return `Unknown market "${String(value)}". Try a code or name such as ${known}, or a CFTC market code such as ${ROOTS.GC!.code}.`;
|
|
146
169
|
}
|
|
147
170
|
/**
|
|
148
171
|
* The code for the ticker under the cursor, when COT is opened without an
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import {
|
|
3
|
+
DELAYED_SEGMENT,
|
|
3
4
|
ChartTableHeader,
|
|
4
5
|
CompositeChart,
|
|
5
6
|
MarketBoardStack,
|
|
@@ -107,7 +108,7 @@ export function CdxPane({ focused, width, height }: PaneProps) {
|
|
|
107
108
|
const asOf = formatAsOf(payload?.asOf ?? null);
|
|
108
109
|
const info = useMemo<PaneFooterSegment[]>(() => [
|
|
109
110
|
...(asOf ? [{ id: "as-of", parts: [{ text: `as of ${asOf}`, tone: "muted" as const }] }] : []),
|
|
110
|
-
...(payload ? [
|
|
111
|
+
...(payload ? [DELAYED_SEGMENT] : []),
|
|
111
112
|
...(resource.data?.stale ? [{ id: "stale", parts: [{ text: "STALE", tone: "warning" as const }] }] : []),
|
|
112
113
|
], [asOf, payload, resource.data?.stale]);
|
|
113
114
|
usePaneStatusFooter({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useMemo } from "react";
|
|
2
2
|
import {
|
|
3
|
+
DELAYED_SEGMENT,
|
|
3
4
|
ChartTableHeader,
|
|
4
5
|
CompositeChart,
|
|
5
6
|
formatBpAxis,
|
|
@@ -112,7 +113,7 @@ export function SovrPane({ focused, width, height }: PaneProps) {
|
|
|
112
113
|
const asOf = formatAsOf(payload?.asOf ?? null);
|
|
113
114
|
const info = useMemo<PaneFooterSegment[]>(() => [
|
|
114
115
|
...(asOf ? [{ id: "as-of", parts: [{ text: `as of ${asOf}`, tone: "muted" as const }] }] : []),
|
|
115
|
-
...(payload ? [
|
|
116
|
+
...(payload ? [DELAYED_SEGMENT] : []),
|
|
116
117
|
...(resource.data?.stale ? [{ id: "stale", parts: [{ text: "STALE", tone: "warning" as const }] }] : []),
|
|
117
118
|
], [asOf, payload, resource.data?.stale]);
|
|
118
119
|
usePaneStatusFooter({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useMemo } from "react";
|
|
2
2
|
import {
|
|
3
|
+
DELAYED_SEGMENT,
|
|
3
4
|
ChartTableHeader,
|
|
4
5
|
CompositeChart,
|
|
5
6
|
formatBpAxis,
|
|
@@ -115,7 +116,7 @@ export function CreditConditionsPane({ paneId, focused, width, height }: PanePro
|
|
|
115
116
|
notices: mixedDates ? ["The indexes carry different observation dates, so the spreads are not one session."] : [],
|
|
116
117
|
});
|
|
117
118
|
const footerInfo = useMemo<PaneFooterSegment[]>(() => [
|
|
118
|
-
...(rows.length > 0 ? [
|
|
119
|
+
...(rows.length > 0 ? [DELAYED_SEGMENT] : []),
|
|
119
120
|
...(partial ? [{ id: "partial", parts: [{ text: `PARTIAL ${rows.length}/${series.length}`, tone: "warning" as const, bold: true }] }] : []),
|
|
120
121
|
...(stale ? [{ id: "stale", parts: [{ text: "STALE", tone: "warning" as const }] }] : []),
|
|
121
122
|
...loadingErrorFooterInfo(loading, error),
|
|
@@ -84,7 +84,34 @@ function withValidStablecoinFields(asset: CryptoMarketAsset): CryptoMarketAsset
|
|
|
84
84
|
return { ...asset, ...(peg ? { peg: null } : {}), ...(range ? { range24h: null } : {}) };
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
const DAY_MS = 86_400_000;
|
|
88
|
+
/** How many UTC days old a coin's latest daily close may be for its previous close to stand, as Gloom Cloud judges a coin quote's. */
|
|
89
|
+
const PREVIOUS_CLOSE_MAX_AGE_DAYS = 2;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A coin of a board from before Gloom Cloud measured its moves since 00:00
|
|
93
|
+
* UTC (a server not yet updated, a copy cached from one), whose change runs
|
|
94
|
+
* over the screener's 24 hours: moved from its previous close instead, the
|
|
95
|
+
* close of its last UTC day while its daily closes reach the last two days.
|
|
96
|
+
* Otherwise that close may be weeks old (Toncoin on 18 Oct 2026), and the
|
|
97
|
+
* coin has no move.
|
|
98
|
+
*/
|
|
99
|
+
function onUtcDayBasis(asset: CryptoMarketAsset, now: number): CryptoMarketAsset {
|
|
100
|
+
const history = asset.history;
|
|
101
|
+
const last = history?.closes.findLastIndex((close) => close != null) ?? -1;
|
|
102
|
+
const lastDay = history && last >= 0 ? Date.parse(`${history.start}T00:00:00Z`) / DAY_MS + last : null;
|
|
103
|
+
const holds = lastDay != null && asset.previousClose != null && asset.previousClose > 0
|
|
104
|
+
&& Math.floor(now / DAY_MS) - lastDay <= PREVIOUS_CLOSE_MAX_AGE_DAYS;
|
|
105
|
+
const change = holds ? asset.price - asset.previousClose! : null;
|
|
106
|
+
return {
|
|
107
|
+
...asset,
|
|
108
|
+
previousClose: holds ? asset.previousClose : null,
|
|
109
|
+
change,
|
|
110
|
+
changePercent: change != null ? (change / asset.previousClose!) * 100 : null,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function validateCryptoMarkets(data: CryptoMarketsPayload, now = Date.now()): CryptoMarketsPayload {
|
|
88
115
|
if (
|
|
89
116
|
!data
|
|
90
117
|
|| data.version !== 1
|
|
@@ -103,7 +130,11 @@ export function validateCryptoMarkets(data: CryptoMarketsPayload): CryptoMarkets
|
|
|
103
130
|
) {
|
|
104
131
|
throw new Error("The server returned an invalid crypto board");
|
|
105
132
|
}
|
|
106
|
-
const
|
|
133
|
+
const legacy = data.dayChangeBasis !== "utc-day";
|
|
134
|
+
const assets = data.assets.map((asset) => {
|
|
135
|
+
const valid = withValidStablecoinFields(asset);
|
|
136
|
+
return legacy ? onUtcDayBasis(valid, now) : valid;
|
|
137
|
+
});
|
|
107
138
|
return assets.every((asset, index) => asset === data.assets[index]) ? data : { ...data, assets };
|
|
108
139
|
}
|
|
109
140
|
|
|
@@ -7,6 +7,7 @@ import { fetchCryptoMarkets, fetchStablecoinSupply } from "./client";
|
|
|
7
7
|
import {
|
|
8
8
|
buildCryptoRows,
|
|
9
9
|
CRYPTO_BOARD_FRESHNESS,
|
|
10
|
+
CRYPTO_CHANGE_BASIS_LABEL,
|
|
10
11
|
formatCryptoPercent,
|
|
11
12
|
formatDayRangeTime,
|
|
12
13
|
formatOffPeg,
|
|
@@ -30,7 +31,9 @@ const COIN_COLUMNS: HeadlessPaneColumn[] = [
|
|
|
30
31
|
{ key: "code", header: "Coin" },
|
|
31
32
|
{ key: "name", header: "Name" },
|
|
32
33
|
{ key: "price", header: "Price", align: "right", format: (_value, row) => String(row.priceText ?? "—") },
|
|
33
|
-
|
|
34
|
+
// Since 00:00 UTC, the day `quote` gives a coin (CRYPTO_CHANGE_BASIS_LABEL): the text says so, as the pane's
|
|
35
|
+
// CHG% UTC does, and an export keeps the header it has always had.
|
|
36
|
+
{ key: "changePercent", header: "Chg %", textHeader: "Chg % UTC", align: "right", format: percent },
|
|
34
37
|
{ key: "return7d", header: "7D %", align: "right", format: percent },
|
|
35
38
|
{ key: "return30d", header: "30D %", align: "right", format: percent },
|
|
36
39
|
{ key: "return1y", header: "1Y %", align: "right", format: percent },
|
|
@@ -167,7 +170,7 @@ export const cryptoBoardHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
167
170
|
pegType: row.pegType,
|
|
168
171
|
} : {}),
|
|
169
172
|
}));
|
|
170
|
-
const metadata = { list: kind, status: data.status, asOf: data.asOf, generatedAt: data.generatedAt };
|
|
173
|
+
const metadata = { list: kind, status: data.status, asOf: data.asOf, generatedAt: data.generatedAt, changeBasis: CRYPTO_CHANGE_BASIS_LABEL };
|
|
171
174
|
if (kind !== "stablecoin") return { rows, errors: data.warnings, metadata };
|
|
172
175
|
const summary = supply ? supplySummaryLine(supply) : null;
|
|
173
176
|
const history = supply ? supplyHistoryTable(supply) : null;
|
|
@@ -57,6 +57,11 @@ export interface CryptoRow extends CryptoRowSupply {
|
|
|
57
57
|
price: number;
|
|
58
58
|
/** The price as the board prints it. */
|
|
59
59
|
priceText: string;
|
|
60
|
+
/**
|
|
61
|
+
* The move since 00:00 UTC, from the previous UTC day's close, as `quote`
|
|
62
|
+
* measures a coin (CRYPTO_CHANGE_BASIS_LABEL); null when the coin has no
|
|
63
|
+
* such close to measure from.
|
|
64
|
+
*/
|
|
60
65
|
changePercent: number | null;
|
|
61
66
|
return7d: number | null;
|
|
62
67
|
return30d: number | null;
|
|
@@ -100,9 +105,28 @@ function closeDaysAgo(asset: CryptoMarketAsset, days: number, now: number): numb
|
|
|
100
105
|
return isFiniteNumber(close) && close > 0 ? close : null;
|
|
101
106
|
}
|
|
102
107
|
|
|
103
|
-
const percentChange = (price: number, reference: number | null) =>
|
|
108
|
+
const percentChange = (price: number, reference: number | null | undefined) =>
|
|
104
109
|
reference != null && reference > 0 ? (price / reference - 1) * 100 : null;
|
|
105
110
|
|
|
111
|
+
/** What the board's day change is measured from, as `quote` labels a coin's: its header says it (CHG% UTC). */
|
|
112
|
+
export const CRYPTO_CHANGE_BASIS_LABEL = "since 00:00 UTC";
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A coin's move since 00:00 UTC, the basis `quote` gives a coin: its live
|
|
116
|
+
* quote's while that quote is measured from the previous close, otherwise the
|
|
117
|
+
* board's, which Gloom Cloud measures the same way (with a live price, from
|
|
118
|
+
* the board's previous close). A coin either says moves over 24 hours, or not
|
|
119
|
+
* at all, has none here, so the column never mixes the two.
|
|
120
|
+
*/
|
|
121
|
+
function utcDayChangePercent(asset: CryptoMarketAsset, quote: Quote | null, price: number): number | null {
|
|
122
|
+
if (quote && quote.changeBasis == null) {
|
|
123
|
+
if (isFiniteNumber(quote.changePercent)) return quote.changePercent;
|
|
124
|
+
if (positive(quote.previousClose)) return percentChange(price, quote.previousClose);
|
|
125
|
+
}
|
|
126
|
+
if (asset.changeBasis != null) return null;
|
|
127
|
+
return quote ? percentChange(price, asset.previousClose) : asset.changePercent;
|
|
128
|
+
}
|
|
129
|
+
|
|
106
130
|
/** A live quote replaces the board's snapshot once it is at least as new. */
|
|
107
131
|
function liveQuote(
|
|
108
132
|
asset: CryptoMarketAsset,
|
|
@@ -134,11 +158,7 @@ function buildCryptoRow(
|
|
|
134
158
|
now = Date.now(),
|
|
135
159
|
): CryptoRow {
|
|
136
160
|
const price = quote?.price ?? asset.price;
|
|
137
|
-
const changePercent = quote
|
|
138
|
-
? quote.changePercent
|
|
139
|
-
: quote
|
|
140
|
-
? percentChange(price, asset.previousClose)
|
|
141
|
-
: asset.changePercent;
|
|
161
|
+
const changePercent = utcDayChangePercent(asset, quote, price);
|
|
142
162
|
// Supply moves slowly; the price is what makes market cap live. Scaling the
|
|
143
163
|
// reported cap keeps the source's supply basis and the board's ranking.
|
|
144
164
|
const marketCap = isFiniteNumber(asset.marketCap)
|
|
@@ -389,7 +409,7 @@ const COLUMN_SPECS: CryptoColumnSpec[] = [
|
|
|
389
409
|
{ id: "code", label: "COIN", width: 7, align: "left" },
|
|
390
410
|
{ id: "name", label: "NAME", width: 10, align: "left", optional: 1 },
|
|
391
411
|
{ id: "price", label: "PRICE", width: 13, align: "right" },
|
|
392
|
-
{ id: "changePercent", label: "CHG%", width: 8, align: "right" },
|
|
412
|
+
{ id: "changePercent", label: "CHG% UTC", title: "Change since 00:00 UTC", width: 8, align: "right" },
|
|
393
413
|
{ id: "return7d", label: "7D%", width: 8, align: "right" },
|
|
394
414
|
{ id: "return30d", label: "30D%", width: 8, align: "right", optional: 3 },
|
|
395
415
|
{ id: "return1y", label: "1Y%", width: 10, align: "right", optional: 5 },
|
|
@@ -415,7 +435,7 @@ const STABLECOIN_COLUMN_SPECS: CryptoColumnSpec[] = [
|
|
|
415
435
|
{ id: "low24hAt", label: "LOW UTC", width: 9, align: "left" },
|
|
416
436
|
{ id: "high24h", label: "24H HIGH", width: 8, align: "right", optional: 1 },
|
|
417
437
|
{ id: "high24hAt", label: "HIGH UTC", width: 9, align: "left", optional: 2 },
|
|
418
|
-
{ id: "changePercent", label: "CHG%", width: 8, align: "right", optional: 5 },
|
|
438
|
+
{ id: "changePercent", label: "CHG% UTC", title: "Change since 00:00 UTC", width: 8, align: "right", optional: 5 },
|
|
419
439
|
{ id: "return7d", label: "7D%", width: 8, align: "right", optional: 6 },
|
|
420
440
|
{ id: "return30d", label: "30D%", width: 8, align: "right", optional: 8 },
|
|
421
441
|
{ id: "return1y", label: "1Y%", width: 10, align: "right", optional: 9 },
|
|
@@ -141,8 +141,9 @@ export async function trialLoadViewSource(
|
|
|
141
141
|
});
|
|
142
142
|
try {
|
|
143
143
|
const loading = loadViewSource(spec, config, controller.signal, registry);
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
loading.catch(() => {
|
|
145
|
+
// Intentional ignore: the race reports load failures; after timeout its result already explains the unchecked source.
|
|
146
|
+
});
|
|
146
147
|
const outcome = await Promise.race([loading, timedOut]);
|
|
147
148
|
if (outcome === "timeout") {
|
|
148
149
|
return [`The source had not answered after ${Math.max(1, Math.round(timeoutMs / 1_000))} s, so it was not checked. The view opens and keeps loading.`];
|
|
@@ -14,6 +14,7 @@ import { PaneStatusBody, TextPromptDialog, usePaneFooter } from "../../../compon
|
|
|
14
14
|
import { useAsyncResource } from "../../../react/async-resource";
|
|
15
15
|
import { useShortcut } from "../../../react/input";
|
|
16
16
|
import { isPlainKey } from "../../../utils/keyboard";
|
|
17
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
17
18
|
import { usePaneInstance, usePaneAppConfig } from "../../../state/app/context";
|
|
18
19
|
import { colors } from "../../../theme/colors";
|
|
19
20
|
import type { PaneProps } from "../../../types/plugin";
|
|
@@ -241,7 +242,7 @@ export function CustomViewPane({ focused, width, height }: PaneProps) {
|
|
|
241
242
|
footer="Shift+Enter newline · Esc cancel"
|
|
242
243
|
/>
|
|
243
244
|
),
|
|
244
|
-
}).catch((
|
|
245
|
+
}).catch(logFailure("custom-view.pane", "The view revision prompt failed"));
|
|
245
246
|
const edited = specText ? parseViewSpecOr(specText) : null;
|
|
246
247
|
if (!edited) return;
|
|
247
248
|
if ("error" in edited) throw new Error(edited.error);
|
|
@@ -268,7 +269,7 @@ export function CustomViewPane({ focused, width, height }: PaneProps) {
|
|
|
268
269
|
selectedChoiceId="reload"
|
|
269
270
|
/>
|
|
270
271
|
),
|
|
271
|
-
}).catch((
|
|
272
|
+
}).catch(logFailure("custom-view.pane", "The view conflict prompt failed"));
|
|
272
273
|
if (choice === "overwrite") {
|
|
273
274
|
const published = await apiClient.publishTeamViewRevision(current.id, {
|
|
274
275
|
spec: edited.spec as unknown as Record<string, unknown>,
|
|
@@ -295,7 +296,7 @@ export function CustomViewPane({ focused, width, height }: PaneProps) {
|
|
|
295
296
|
selectedChoiceId={defaultTeam ?? undefined}
|
|
296
297
|
/>
|
|
297
298
|
),
|
|
298
|
-
}).catch((
|
|
299
|
+
}).catch(logFailure("custom-view.pane", "The view team prompt failed"));
|
|
299
300
|
if (!teamId) return;
|
|
300
301
|
const name = await dialog.prompt<string>({
|
|
301
302
|
content: (context: PromptContext<string>) => (
|
|
@@ -308,7 +309,7 @@ export function CustomViewPane({ focused, width, height }: PaneProps) {
|
|
|
308
309
|
width={40}
|
|
309
310
|
/>
|
|
310
311
|
),
|
|
311
|
-
}).catch((
|
|
312
|
+
}).catch(logFailure("custom-view.pane", "The view name prompt failed"));
|
|
312
313
|
if (!name) return;
|
|
313
314
|
const created = await apiClient.createTeamView(teamId, {
|
|
314
315
|
name,
|
|
@@ -15,6 +15,7 @@ import type { Quote } from "../../../types/financials";
|
|
|
15
15
|
import { regularSessionValuationPrice } from "../../../market-data/quotes/regular-session-valuation";
|
|
16
16
|
import { dividendPriceStatus, dividendQuotePriceMetadata } from "./reference-price";
|
|
17
17
|
import { mapWithConcurrency } from "../../../utils/concurrency";
|
|
18
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
18
19
|
import { projectDividendRanking, roundedRatio, type DividendRankingEntry } from "./ranking";
|
|
19
20
|
import { REPORT_FILTER, REPORT_ROWS } from "../shared/headless-options";
|
|
20
21
|
import type { FundBondFacts } from "../../../api-client/fund-facts";
|
|
@@ -50,6 +51,9 @@ export interface DividendYieldHeadlessDependencies {
|
|
|
50
51
|
loadTreasury?(context: HeadlessPaneContext): Promise<TreasuryTenor[] | null>;
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
/** Already logged at the quote boundary; keep it distinct from absent bond facts. */
|
|
55
|
+
class BondClassificationError extends Error {}
|
|
56
|
+
|
|
53
57
|
const defaultDependencies: DividendYieldHeadlessDependencies = {
|
|
54
58
|
async loadData(symbol, context) {
|
|
55
59
|
let currentPrice: number | null = null;
|
|
@@ -63,7 +67,8 @@ const defaultDependencies: DividendYieldHeadlessDependencies = {
|
|
|
63
67
|
currentPrice = regularSessionValuationPrice(quote);
|
|
64
68
|
currentPriceCurrency = quote.currency;
|
|
65
69
|
referenceQuote = quote;
|
|
66
|
-
} catch {
|
|
70
|
+
} catch (error) {
|
|
71
|
+
logFailure("dividend-yield.headless", "Read dividend reference quote failed")(error);
|
|
67
72
|
currentPrice = null;
|
|
68
73
|
}
|
|
69
74
|
const data = await fetchDividendData(symbol, currentPrice, exchange, currentPriceCurrency);
|
|
@@ -75,7 +80,11 @@ const defaultDependencies: DividendYieldHeadlessDependencies = {
|
|
|
75
80
|
async loadBond(symbol, context) {
|
|
76
81
|
const instrument = await context.resolveInstrument?.(symbol);
|
|
77
82
|
const exchange = instrument?.exchange ?? "";
|
|
78
|
-
const quote = await context.marketData.getQuote(symbol, exchange)
|
|
83
|
+
const quote = await context.marketData.getQuote(symbol, exchange)
|
|
84
|
+
.catch((error) => {
|
|
85
|
+
logFailure("dividend-yield.headless", "Read bond fund classification quote failed")(error);
|
|
86
|
+
throw new BondClassificationError("Bond fund quote check failed");
|
|
87
|
+
});
|
|
79
88
|
const venue = exchange || quote?.exchangeName || "";
|
|
80
89
|
if (resolveTickerInstrumentKind(null, { quote }) !== "fund" || (venue && !isUsListingExchange(venue))) return null;
|
|
81
90
|
return fetchFundBondFacts(instrument?.symbol ?? symbol, context.apiClient);
|
|
@@ -125,7 +134,7 @@ export function projectDividendYieldHeadless(
|
|
|
125
134
|
const metrics = data.metrics;
|
|
126
135
|
const currency = data.currency ?? data.payments[0]?.currency ?? "";
|
|
127
136
|
const priceStatus = dividendPriceStatus(data.price, data.priceAsOf, data.priceStale);
|
|
128
|
-
const errors = [data.historyError, data.summaryError].filter((error): error is string => !!error);
|
|
137
|
+
const errors = [data.historyError, data.summaryError, tax?.checkError].filter((error): error is string => !!error);
|
|
129
138
|
|
|
130
139
|
return {
|
|
131
140
|
...(errors.length > 0 ? { complete: false, errors } : {}),
|
|
@@ -249,15 +258,26 @@ export function createDividendYieldHeadless(
|
|
|
249
258
|
// One symbol is its full report; several are one table ranked by yield.
|
|
250
259
|
if (args.symbols.length <= 1) {
|
|
251
260
|
const symbol = args.symbols[0] ?? String(args.argument);
|
|
261
|
+
let bondCheckError: string | undefined;
|
|
252
262
|
const [data, bond, treasury] = await Promise.all([
|
|
253
263
|
dependencies.loadData(symbol, context),
|
|
254
|
-
//
|
|
255
|
-
dependencies.loadBond?.(symbol, context)
|
|
256
|
-
|
|
264
|
+
// Optional enrichment without a tax rate; a requested tax check must distinguish failure from absence.
|
|
265
|
+
dependencies.loadBond?.(symbol, context)
|
|
266
|
+
.catch((error) => {
|
|
267
|
+
if (error instanceof BondClassificationError) {
|
|
268
|
+
bondCheckError = error.message;
|
|
269
|
+
} else {
|
|
270
|
+
logFailure("dividend-yield.headless", "Read bond fund facts for report failed")(error);
|
|
271
|
+
bondCheckError = "Bond fund facts check failed";
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}) ?? null,
|
|
275
|
+
rates ? dependencies.loadTreasury?.(context)
|
|
276
|
+
.catch(logFailure("dividend-yield.headless", "Read Treasury curve for tax comparison failed", null)) ?? null : null,
|
|
257
277
|
]);
|
|
258
278
|
const forward = data.metrics.lapse ? null : distributionBasis(data.metrics.forwardYield, "forward", data.metrics.forwardRateAsOf ?? null);
|
|
259
279
|
const tax = rates ? analyzeTaxEquivalent({
|
|
260
|
-
symbol: symbol.toUpperCase(), bond, rates, treasury,
|
|
280
|
+
symbol: symbol.toUpperCase(), bond, rates, treasury, checkError: bondCheckError,
|
|
261
281
|
distribution: forward ?? distributionBasis(data.metrics.trailingYield, "trailing"),
|
|
262
282
|
}) : null;
|
|
263
283
|
return projectDividendYieldHeadless(data, args, { bond, tax });
|
|
@@ -275,7 +295,9 @@ export function createDividendYieldHeadless(
|
|
|
275
295
|
}
|
|
276
296
|
const entries = await mapWithConcurrency(args.symbols, RANKING_CONCURRENCY, async (symbol): Promise<DividendRankingEntry> => {
|
|
277
297
|
// A fund's bond facts never fail its row: without them it ranks as before.
|
|
278
|
-
const bond = dependencies.loadBond?.(symbol, context)
|
|
298
|
+
const bond = dependencies.loadBond?.(symbol, context)
|
|
299
|
+
.catch((error) => error instanceof BondClassificationError ? null
|
|
300
|
+
: logFailure("dividend-yield.headless", "Read bond fund facts for ranking failed", null)(error)) ?? null;
|
|
279
301
|
try {
|
|
280
302
|
return { symbol, data: await dependencies.loadData(symbol, context), bond: await bond };
|
|
281
303
|
} catch (error) {
|
|
@@ -3,6 +3,7 @@ import { regularSessionValuationPrice } from "../../../market-data/quotes/regula
|
|
|
3
3
|
import { resolveCurrencyUnit } from "../../../utils/currency-units";
|
|
4
4
|
import { cashInMajorUnits } from "../../../utils/dividend-cash";
|
|
5
5
|
import { dividendReferencePrice } from "../../../utils/trailing-dividend";
|
|
6
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
6
7
|
import { buildDividendMetrics, INCOMPLETE_DIVIDEND_HISTORY, MISSING_DIVIDEND_CURRENCY, toDividendPayment, type DividendData } from "./client";
|
|
7
8
|
import { dividendQuotePriceMetadata } from "./reference-price";
|
|
8
9
|
|
|
@@ -17,7 +18,8 @@ export async function fetchProviderDividendData(
|
|
|
17
18
|
if (!provider.getCorporateActions) throw new Error("Dividend history source unavailable.");
|
|
18
19
|
const [sourceActions, quote] = await Promise.all([
|
|
19
20
|
provider.getCorporateActions(symbol, exchange),
|
|
20
|
-
provider.getQuote(symbol, exchange)
|
|
21
|
+
provider.getQuote(symbol, exchange)
|
|
22
|
+
.catch(logFailure("dividend-yield.provider-client", "Read dividend reference quote failed", null)),
|
|
21
23
|
]);
|
|
22
24
|
// In the major unit as the history arrives, as the provider router serves it.
|
|
23
25
|
const actions = cashInMajorUnits(sourceActions);
|
|
@@ -70,8 +70,8 @@ const defaultDependencies: EarningsCallsHeadlessDependencies = {
|
|
|
70
70
|
};
|
|
71
71
|
|
|
72
72
|
/** A refused read says what the lock note says (CALLS is Pro whole), so a free account meets the same words as a signed-out one. */
|
|
73
|
-
function explainDenied(ticker: string | null) {
|
|
74
|
-
return (error: unknown): never => { throw explainRefusal(error, callsLockNote, { symbol: ticker }); };
|
|
73
|
+
function explainDenied(ticker: string | null, context: HeadlessPaneContext) {
|
|
74
|
+
return async (error: unknown): Promise<never> => { throw await explainRefusal(error, callsLockNote, { symbol: ticker, context }); };
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
function callRow(call: CloudEarningsCallPayload) {
|
|
@@ -285,7 +285,7 @@ export function createEarningsCallsHeadless(
|
|
|
285
285
|
},
|
|
286
286
|
async load(args, context) {
|
|
287
287
|
const ticker = typeof args.argument === "string" ? args.argument : null;
|
|
288
|
-
const calls = await dependencies.loadCalls(ticker, 200, context).catch(explainDenied(ticker));
|
|
288
|
+
const calls = await dependencies.loadCalls(ticker, 200, context).catch(explainDenied(ticker, context));
|
|
289
289
|
if (!ticker || !readsTranscript(args)) {
|
|
290
290
|
return projectEarningsCallsHeadless(calls, { ...args, options: { ...args.options, limit: args.options.limit ?? 50 } });
|
|
291
291
|
}
|
|
@@ -299,7 +299,7 @@ export function createEarningsCallsHeadless(
|
|
|
299
299
|
}
|
|
300
300
|
throw new Error(`No ${quarter} earnings call found for ${ticker}.`);
|
|
301
301
|
}
|
|
302
|
-
const transcript = await dependencies.loadTranscript(selected.id, context).catch(explainDenied(ticker));
|
|
302
|
+
const transcript = await dependencies.loadTranscript(selected.id, context).catch(explainDenied(ticker, context));
|
|
303
303
|
if (isPendingTranscript(transcript)) {
|
|
304
304
|
throw new Error(`Transcript for ${ticker} ${quarter} is still being produced.`);
|
|
305
305
|
}
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import type { PaneLock } from "../../../types/plugin";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveHeadlessInstrument } from "../shared/headless-market-data";
|
|
3
|
+
import { proLockNote, type FreeRoute } from "../shared/pro-lock-note";
|
|
4
|
+
|
|
5
|
+
const EVENTS_ROUTE: FreeRoute = {
|
|
6
|
+
text: (ticker, company) => `gloomberb events ${ticker} lists ${company}'s earnings dates, with the EPS reported at each and the next estimate.`,
|
|
7
|
+
answers: async (symbol, context) => {
|
|
8
|
+
const { symbol: ticker, exchange } = await resolveHeadlessInstrument(context, symbol);
|
|
9
|
+
return ((await context.marketData.getCorporateActions?.(ticker, exchange))?.earnings.length ?? 0) > 0;
|
|
10
|
+
},
|
|
11
|
+
};
|
|
3
12
|
|
|
4
13
|
/**
|
|
5
14
|
* CALLS is Pro whole, the list of calls included. `events` needs no account
|
|
6
15
|
* and lists a company's earnings dates, each with the EPS it reported and the
|
|
7
16
|
* next one's estimate, without the call or its transcript.
|
|
8
17
|
*/
|
|
9
|
-
export function callsLockNote(lock: PaneLock): string {
|
|
18
|
+
export function callsLockNote(lock: PaneLock): Promise<string> {
|
|
10
19
|
return proLockNote(lock, {
|
|
11
20
|
token: "CALLS",
|
|
12
21
|
argument: "optional",
|
|
13
22
|
does: "it reads each earnings call into a transcript with speaker turns, analyst Q&A and extracted guidance",
|
|
14
23
|
shows: "it",
|
|
15
|
-
free:
|
|
24
|
+
free: [EVENTS_ROUTE],
|
|
16
25
|
});
|
|
17
26
|
}
|
|
@@ -14,7 +14,8 @@ const CACHE_POLICY = {
|
|
|
14
14
|
} as const;
|
|
15
15
|
|
|
16
16
|
export type ImpactFilter = "high" | "medium" | "low" | "all";
|
|
17
|
-
|
|
17
|
+
/** A region the pane cycles through, or one country by its two-letter code (`econ --country TR`). */
|
|
18
|
+
export type CountryFilter = "all" | "US" | "G7" | "EU" | (string & {});
|
|
18
19
|
|
|
19
20
|
export const FILTER_CYCLE: ImpactFilter[] = ["all", "high", "medium", "low"];
|
|
20
21
|
/**
|
|
@@ -187,7 +188,38 @@ export function matchesCountry(event: EconEvent, filter: CountryFilter): boolean
|
|
|
187
188
|
if (filter === "US") return event.country === "US";
|
|
188
189
|
if (filter === "G7") return G7_COUNTRIES.has(event.country);
|
|
189
190
|
if (filter === "EU") return event.country === "EU";
|
|
190
|
-
return
|
|
191
|
+
return event.country === filter;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* How long after its release time a high-impact print is still news: the
|
|
196
|
+
* calendar is asked for it this long, and a print that turns up later is
|
|
197
|
+
* recorded without a toast.
|
|
198
|
+
*/
|
|
199
|
+
export const PRINT_PENDING_MS = 20 * 60 * 1000;
|
|
200
|
+
|
|
201
|
+
/** High-impact releases whose time has come in the last {@link PRINT_PENDING_MS} and that have no actual yet. */
|
|
202
|
+
export function pendingHighImpactPrints(events: readonly EconEvent[], now: number): EconEvent[] {
|
|
203
|
+
return events.filter((event) => {
|
|
204
|
+
if (event.impact !== "high" || event.actual) return false;
|
|
205
|
+
const releasedFor = now - event.date.getTime();
|
|
206
|
+
return releasedFor >= 0 && releasedFor <= PRINT_PENDING_MS;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function hasPendingHighImpactPrint(events: readonly EconEvent[], now: number): boolean {
|
|
211
|
+
return pendingHighImpactPrints(events, now).length > 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** High-impact rows that were blank in `previous` and have an actual in `next`. */
|
|
215
|
+
export function landedHighImpactPrints(
|
|
216
|
+
previous: ReadonlyMap<string, string | null>,
|
|
217
|
+
next: readonly EconEvent[],
|
|
218
|
+
): EconEvent[] {
|
|
219
|
+
return next.filter((event) => {
|
|
220
|
+
if (event.impact !== "high" || !event.actual || !previous.has(event.id)) return false;
|
|
221
|
+
return !previous.get(event.id);
|
|
222
|
+
});
|
|
191
223
|
}
|
|
192
224
|
|
|
193
225
|
export function formatCountdown(ms: number): string {
|
|
@@ -228,6 +260,10 @@ export function getCalendarCache(options?: { allowExpired?: boolean }): EconCale
|
|
|
228
260
|
return cache.get(CACHE_KEY, options);
|
|
229
261
|
}
|
|
230
262
|
|
|
263
|
+
export function subscribeCalendar(listener: () => void): () => void {
|
|
264
|
+
return cache.subscribe(CACHE_KEY, listener);
|
|
265
|
+
}
|
|
266
|
+
|
|
231
267
|
export interface EconCalendarLoadResult extends EconCalendarCacheEntry {
|
|
232
268
|
refreshError?: string;
|
|
233
269
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fredSeriesUrl } from "../../../sources/gloomberb-cloud/fred-series";
|
|
1
|
+
import { fredSeriesUrl, seriesNotOnFred } from "../../../sources/gloomberb-cloud/fred-series";
|
|
2
2
|
import type { SeriesDefinition, SeriesFrequency } from "../../../series/types";
|
|
3
3
|
|
|
4
4
|
type FredRow = readonly [seriesId: string, name: string, unit: string, frequency: SeriesFrequency, tags?: readonly string[]];
|
|
@@ -8,6 +8,8 @@ type FredRow = readonly [seriesId: string, name: string, unit: string, frequency
|
|
|
8
8
|
* maturities (UST:2Y and so on, from the yield curve) and the credit spreads
|
|
9
9
|
* and yields (from the credit plugin). Ids follow the server's allowlist: a
|
|
10
10
|
* series added there is listed here to reach the catalog.
|
|
11
|
+
* Türkiye's CPI is served the same way but is not on FRED; its rows name
|
|
12
|
+
* TurkStat (`seriesNotOnFred`).
|
|
11
13
|
*/
|
|
12
14
|
const GROUPS: ReadonlyArray<{ group: string; rows: readonly FredRow[] }> = [
|
|
13
15
|
{
|
|
@@ -123,6 +125,8 @@ const GROUPS: ReadonlyArray<{ group: string; rows: readonly FredRow[] }> = [
|
|
|
123
125
|
["T5YIE", "5Y breakeven inflation", "%", "daily", ["breakeven", "expectations", "TIPS"]],
|
|
124
126
|
["T10YIE", "10Y breakeven inflation", "%", "daily", ["breakeven", "expectations", "TIPS"]],
|
|
125
127
|
["T5YIFR", "5Y, 5Y forward inflation expectation", "%", "daily", ["breakeven", "expectations", "5y5y"]],
|
|
128
|
+
["TURCPIYOY", "Türkiye CPI, y/y", "%", "monthly", ["CPI", "headline", "Turkey", "TR", "inflation", "lira"]],
|
|
129
|
+
["TURCPIMOM", "Türkiye CPI, m/m", "%", "monthly", ["CPI", "headline", "Turkey", "TR", "inflation", "lira"]],
|
|
126
130
|
],
|
|
127
131
|
},
|
|
128
132
|
{
|
|
@@ -191,7 +195,7 @@ export const FRED_CATALOG_SERIES: readonly SeriesDefinition[] = GROUPS.flatMap((
|
|
|
191
195
|
frequency,
|
|
192
196
|
group,
|
|
193
197
|
...(tags?.length ? { tags } : {}),
|
|
194
|
-
publisher: "FRED",
|
|
198
|
+
publisher: seriesNotOnFred(seriesId)?.publisher ?? "FRED",
|
|
195
199
|
url: fredSeriesUrl(seriesId),
|
|
196
200
|
expression: `FRED:${seriesId}`,
|
|
197
201
|
}))
|
|
@@ -51,12 +51,23 @@ const SERIES_MAP: Record<string, FredMapping> = {
|
|
|
51
51
|
"unit labor costs q/q": { seriesId: "ULCNFB", displayMode: "qoq-annualized", relatedTickers: ["SPY", "^TNX"] },
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
/** Other countries' releases Gloom Cloud serves a series for, by country and normalized title. */
|
|
55
|
+
const COUNTRY_SERIES_MAP: Record<string, Record<string, FredMapping>> = {
|
|
56
|
+
// TurkStat's CPI, published as percent changes; the related ticker is the US-listed MSCI Turkey ETF.
|
|
57
|
+
TR: {
|
|
58
|
+
"cpi y/y": { seriesId: "TURCPIYOY", displayMode: "level", relatedTickers: ["TUR"] },
|
|
59
|
+
"cpi m/m": { seriesId: "TURCPIMOM", displayMode: "level", relatedTickers: ["TUR"] },
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
54
63
|
function normalizeEventTitle(title: string): string {
|
|
55
64
|
return title.toLowerCase().trim();
|
|
56
65
|
}
|
|
57
66
|
|
|
58
67
|
export function resolveFredMapping(eventTitle: string, country: string): FredMapping | null {
|
|
59
|
-
|
|
68
|
+
const elsewhere = COUNTRY_SERIES_MAP[country];
|
|
69
|
+
if (elsewhere) return elsewhere[normalizeEventTitle(eventTitle)] ?? null;
|
|
70
|
+
// Otherwise only US events have FRED data
|
|
60
71
|
if (country !== "US" && country !== "USD") return null;
|
|
61
72
|
|
|
62
73
|
const normalized = normalizeEventTitle(eventTitle);
|