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
|
@@ -39,6 +39,7 @@ import { ResearchTabKeysProvider, useResearchTabKeysHost } from "./research-tab-
|
|
|
39
39
|
import { ResearchTabNavigationProvider } from "./research-tab-navigation";
|
|
40
40
|
import { usePluginAppActions } from "../../../public/react";
|
|
41
41
|
import { resolveTickerInstrumentKind } from "../../../tickers/instrument-kind";
|
|
42
|
+
import { ResearchTradeWorkspace } from "../broker-trading/dock";
|
|
42
43
|
|
|
43
44
|
const TICKER_RESEARCH_TAB_COMMIT_DELAY_MS = 120;
|
|
44
45
|
/** A tab counts as viewed once it stays open this long, not when h/l passes over it. */
|
|
@@ -207,7 +208,9 @@ export function TickerResearchPane({ focused, width, height }: PaneProps) {
|
|
|
207
208
|
const quoteFooterActive = resolvedTabId === "overview" || resolvedTabId === "chart";
|
|
208
209
|
const cloudAccess = useCloudAccessFooter({
|
|
209
210
|
delayLabel: tf("{count}m", { count: CLOUD_QUOTE_DELAY_MINUTES }),
|
|
210
|
-
|
|
211
|
+
// A listing that no longer trades has no delay to speak of: its quote is its last session's close.
|
|
212
|
+
degraded: !financials?.quote?.delisting && financials?.quote?.priceObservation !== "nav"
|
|
213
|
+
&& financials?.quote?.dataSource !== "live",
|
|
211
214
|
focused: focused && quoteFooterActive,
|
|
212
215
|
segmentId: "ticker-research-access",
|
|
213
216
|
shortcutScope: "ticker-research:upgrade",
|
|
@@ -359,16 +362,14 @@ export function TickerResearchPane({ focused, width, height }: PaneProps) {
|
|
|
359
362
|
);
|
|
360
363
|
}
|
|
361
364
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
<Box height={contentHeight} flexGrow={1} flexBasis={0} overflow="hidden">
|
|
367
|
-
<ResearchTabKeysProvider value={showTabs ? researchTabKeys.value : null}>
|
|
365
|
+
const tradeAvailable = visibleTabIds.has("broker-trade");
|
|
366
|
+
// Trade shows Chart beside its ticket: the workspace names the tab it shows.
|
|
367
|
+
const renderTabs = (contentWidth: number, contentFocused: boolean, shownTabId = resolvedTabId) => (
|
|
368
|
+
<ResearchTabKeysProvider value={showTabs ? researchTabKeys.value : null}>
|
|
368
369
|
{tickerResearchTabs.map((tab) => {
|
|
369
|
-
if (!renderedTabIds.has(tab.id) || !visibleTabIds.has(tab.id)) return null;
|
|
370
|
+
if (tradeAvailable && tab.id === "broker-trade" || !(renderedTabIds.has(tab.id) || tab.id === shownTabId) || !visibleTabIds.has(tab.id)) return null;
|
|
370
371
|
const TickerResearchTab = tab.component;
|
|
371
|
-
const isActive =
|
|
372
|
+
const isActive = shownTabId === tab.id;
|
|
372
373
|
return (
|
|
373
374
|
<Box
|
|
374
375
|
key={tab.id}
|
|
@@ -383,9 +384,9 @@ export function TickerResearchPane({ focused, width, height }: PaneProps) {
|
|
|
383
384
|
<NestedPaneTabs>
|
|
384
385
|
<ResearchTabNavigationProvider value={showTabs ? openResearchTab : null}>
|
|
385
386
|
<TickerResearchTab
|
|
386
|
-
width={
|
|
387
|
+
width={contentWidth}
|
|
387
388
|
height={contentHeight}
|
|
388
|
-
focused={
|
|
389
|
+
focused={contentFocused && isActive}
|
|
389
390
|
onCapture={isActive ? handlePluginCapture : ignorePluginCapture}
|
|
390
391
|
/>
|
|
391
392
|
</ResearchTabNavigationProvider>
|
|
@@ -394,8 +395,14 @@ export function TickerResearchPane({ focused, width, height }: PaneProps) {
|
|
|
394
395
|
</Box>
|
|
395
396
|
);
|
|
396
397
|
})}
|
|
397
|
-
|
|
398
|
-
</Box>
|
|
399
|
-
</Box>
|
|
398
|
+
</ResearchTabKeysProvider>
|
|
400
399
|
);
|
|
400
|
+
return <Box flexDirection="column" flexGrow={1} flexBasis={0} overflow="hidden">
|
|
401
|
+
{tabStrip}
|
|
402
|
+
<Box height={contentHeight} flexGrow={1} flexBasis={0} overflow="hidden">
|
|
403
|
+
{tradeAvailable ? <ResearchTradeWorkspace width={width} height={contentHeight} focused={focused} symbol={ticker.metadata.ticker}
|
|
404
|
+
tabId={resolvedTabId} selectTab={selectResearchTab} onCapture={handlePluginCapture} locked={!!paneSettings.hideTabs}>{renderTabs}</ResearchTradeWorkspace>
|
|
405
|
+
: renderTabs(width, focused)}
|
|
406
|
+
</Box>
|
|
407
|
+
</Box>;
|
|
401
408
|
}
|
|
@@ -2,6 +2,7 @@ import type { PaneFooterSegment } from "../../../components/layout/pane/footer";
|
|
|
2
2
|
import { t } from "../../../i18n";
|
|
3
3
|
import { isQuoteStaleForCurrentSession } from "../../../market-data/quotes/freshness";
|
|
4
4
|
import { formatQuoteNavAsOf } from "../../../market-data/quotes/time";
|
|
5
|
+
import { delistingLabel } from "../../../market-data/quotes/delisting";
|
|
5
6
|
import type { Quote } from "../../../types/financials";
|
|
6
7
|
import { displayWidth } from "../../../utils/format";
|
|
7
8
|
|
|
@@ -12,7 +13,10 @@ export function tickerQuoteFooterInfo(
|
|
|
12
13
|
): PaneFooterSegment[] {
|
|
13
14
|
const info: PaneFooterSegment[] = [];
|
|
14
15
|
const navAsOf = formatQuoteNavAsOf(quote);
|
|
15
|
-
if (
|
|
16
|
+
if (quote?.delisting) {
|
|
17
|
+
// A listing that no longer trades: its quote is its last trade, said once with its date.
|
|
18
|
+
info.push({ id: "ticker-research-stale", parts: [{ text: delistingLabel(quote.delisting), tone: "warning" }] });
|
|
19
|
+
} else if (isQuoteStaleForCurrentSession(quote)) {
|
|
16
20
|
const status: PaneFooterSegment = { id: "ticker-research-stale", parts: [{ text: t("Stale quote"), tone: "warning" }] };
|
|
17
21
|
const timestamp = new Date(quote!.lastUpdated);
|
|
18
22
|
if (navAsOf) {
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from "../../../../market-data/market/status";
|
|
21
21
|
import { t } from "../../../../i18n";
|
|
22
22
|
import { isQuoteStaleForCurrentSession } from "../../../../market-data/quotes/freshness";
|
|
23
|
+
import { delistingLabel, delistingStateLabel } from "../../../../market-data/quotes/delisting";
|
|
23
24
|
import { formatQuoteNavAsOf } from "../../../../market-data/quotes/time";
|
|
24
25
|
import { useQuoteFlashDirection } from "../../../../components/quote-flash";
|
|
25
26
|
import { followLiveSparklinePrice } from "../../../../components/price-sparkline/model";
|
|
@@ -89,6 +90,8 @@ interface QuoteStatus {
|
|
|
89
90
|
failed: boolean;
|
|
90
91
|
text: string;
|
|
91
92
|
stale?: boolean;
|
|
93
|
+
/** DELISTED or INACTIVE: the listing no longer trades, and `text` says since when. */
|
|
94
|
+
ended?: string;
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
function resolveQuoteStatus(entry: QueryEntry<Quote> | null, symbol: string, quote: Quote | null | undefined): QuoteStatus {
|
|
@@ -98,6 +101,7 @@ function resolveQuoteStatus(entry: QueryEntry<Quote> | null, symbol: string, quo
|
|
|
98
101
|
? { failed: true, text: `${symbol} not recognized` }
|
|
99
102
|
: { failed: true, text: error.message || "Quote unavailable" };
|
|
100
103
|
}
|
|
104
|
+
if (quote?.delisting) return { failed: true, text: delistingLabel(quote.delisting), stale: true, ended: delistingStateLabel(quote.delisting) };
|
|
101
105
|
if (isQuoteStaleForCurrentSession(quote)) return { failed: true, text: "Stale quote", stale: true };
|
|
102
106
|
if (entry?.phase === "ready") return { failed: false, text: "No quote data" };
|
|
103
107
|
return { failed: false, text: "Loading quote..." };
|
|
@@ -175,8 +179,9 @@ export function QuoteMonitorCard({
|
|
|
175
179
|
// One decimal count per instrument, so streamed ticks never narrow or widen the price column.
|
|
176
180
|
const priceOptions = liveQuoteFormatOptions(quote, currency, assetCategory, cachedFinancials?.quoteMetadata?.instrumentType);
|
|
177
181
|
const priceText = display ? formatMarketPriceWithCurrency(display.price, currency, priceOptions) : "";
|
|
178
|
-
|
|
179
|
-
const
|
|
182
|
+
// A listing that no longer trades shows its last close alone: no move, not even a dash for one.
|
|
183
|
+
const changePercentText = display && !quote?.delisting ? formatPercentRaw(display.changePercent) : "";
|
|
184
|
+
const changeValueText = display && !quote?.delisting ? formatSignedMarketPrice(display.change, priceOptions) : "";
|
|
180
185
|
const priceColumnWidth = Math.max(priceText.length, changePercentText.length + changeValueText.length + 1);
|
|
181
186
|
const nameMaxWidth = Math.max(10, width - priceColumnWidth - (nativePaneChrome ? 5 : 3));
|
|
182
187
|
// The terminal sets the line beside the change, under the symbol (or after it in a
|
|
@@ -187,6 +192,8 @@ export function QuoteMonitorCard({
|
|
|
187
192
|
const extendedParts = display && extended
|
|
188
193
|
? extendedLineParts(extended, formatMarketPriceWithCurrency(extended.price, currency, priceOptions), (text) => displayWidth(text) <= extendedRoom)
|
|
189
194
|
: null;
|
|
195
|
+
// A desktop card says a listing no longer trades on a row of its own.
|
|
196
|
+
const endedRow = nativePaneChrome && quoteFailed && !!quoteStatus.ended && !extendedParts;
|
|
190
197
|
const extendedColor = quoteFailed ? colors.textDim : priceColor(extended?.change ?? 0);
|
|
191
198
|
const extendedDim = !!flashDirection && headlineHolds;
|
|
192
199
|
const sparklineRange = resolvePriceSparklineRange(priceHistory, chartPeriod);
|
|
@@ -254,7 +261,7 @@ export function QuoteMonitorCard({
|
|
|
254
261
|
<Text attributes={TextAttributes.BOLD} fg={symbolFg} bg={symbolBg} style={desktopSymbolStyle}>
|
|
255
262
|
{symbol}
|
|
256
263
|
</Text>
|
|
257
|
-
<Text fg={quoteStatus.failed ? colors.negative : colors.textDim}>{quoteStatus.text}</Text>
|
|
264
|
+
<Text fg={quoteStatus.ended ? colors.warning : quoteStatus.failed ? colors.negative : colors.textDim}>{quoteStatus.text}</Text>
|
|
258
265
|
</Box>
|
|
259
266
|
) : nativePaneChrome ? (
|
|
260
267
|
<Box
|
|
@@ -276,7 +283,7 @@ export function QuoteMonitorCard({
|
|
|
276
283
|
minWidth={0}
|
|
277
284
|
style={{
|
|
278
285
|
gridColumn: "1",
|
|
279
|
-
gridRow: extendedParts ? "1" : "1 / span 2",
|
|
286
|
+
gridRow: extendedParts || endedRow ? "1" : "1 / span 2",
|
|
280
287
|
minWidth: 0,
|
|
281
288
|
overflow: "hidden",
|
|
282
289
|
}}
|
|
@@ -284,8 +291,8 @@ export function QuoteMonitorCard({
|
|
|
284
291
|
<Text attributes={TextAttributes.BOLD} fg={symbolFg} bg={symbolBg} style={desktopSymbolStyle}>
|
|
285
292
|
{symbol}
|
|
286
293
|
</Text>
|
|
287
|
-
{quoteFailed && (
|
|
288
|
-
<Text fg={colors.negative} style={{ fontSize: "var(--gloom-type-ui)", lineHeight: "14px" }}>
|
|
294
|
+
{quoteFailed && !endedRow && (
|
|
295
|
+
<Text fg={quoteStatus.ended ? colors.warning : colors.negative} style={{ fontSize: "var(--gloom-type-ui)", lineHeight: "14px" }}>
|
|
289
296
|
{quoteStatus.text}
|
|
290
297
|
</Text>
|
|
291
298
|
)}
|
|
@@ -332,6 +339,28 @@ export function QuoteMonitorCard({
|
|
|
332
339
|
</Box>
|
|
333
340
|
</Box>
|
|
334
341
|
|
|
342
|
+
{endedRow && (
|
|
343
|
+
// Under the price, across the card, so the date is never cut on a narrow card;
|
|
344
|
+
// backed like the price so the sparkline does not run through it.
|
|
345
|
+
<Text
|
|
346
|
+
fg={colors.warning}
|
|
347
|
+
style={{
|
|
348
|
+
gridColumn: "1 / -1",
|
|
349
|
+
gridRow: "2",
|
|
350
|
+
justifySelf: "start",
|
|
351
|
+
alignSelf: "start",
|
|
352
|
+
fontSize: "var(--gloom-type-ui)",
|
|
353
|
+
lineHeight: "14px",
|
|
354
|
+
whiteSpace: "nowrap",
|
|
355
|
+
backgroundColor: colors.bg,
|
|
356
|
+
borderRadius: "var(--gloom-radius-sm)",
|
|
357
|
+
boxShadow: `0 0 0 2px ${colors.bg}`,
|
|
358
|
+
}}
|
|
359
|
+
>
|
|
360
|
+
{quoteStatus.text}
|
|
361
|
+
</Text>
|
|
362
|
+
)}
|
|
363
|
+
|
|
335
364
|
{extendedParts && (
|
|
336
365
|
<ExtendedSessionLine
|
|
337
366
|
parts={extendedParts}
|
|
@@ -385,7 +414,7 @@ export function QuoteMonitorCard({
|
|
|
385
414
|
</Box>
|
|
386
415
|
<Box flexDirection="column">
|
|
387
416
|
<FigureText fg={changeColor} dim={flashing} style={figureHalo}>
|
|
388
|
-
{compactQuoteFailure ? `${priceText} · ${quoteStatus.stale ? "STALE" : "ERROR"}` : priceText}
|
|
417
|
+
{compactQuoteFailure ? `${priceText} · ${quoteStatus.ended ?? (quoteStatus.stale ? "STALE" : "ERROR")}` : priceText}
|
|
389
418
|
</FigureText>
|
|
390
419
|
<Box flexDirection="row" gap={1}>
|
|
391
420
|
<FigureText part="sub" fg={changeColor} dim={flashing} style={figureHalo}>{changePercentText}</FigureText>
|
|
@@ -432,7 +461,7 @@ export function QuoteMonitorCard({
|
|
|
432
461
|
)}
|
|
433
462
|
|
|
434
463
|
{quoteFailed && !compactQuoteFailure && (
|
|
435
|
-
<Box height={1} overflow="hidden"><Text fg={colors.negative}>{quoteStatus.text}</Text></Box>
|
|
464
|
+
<Box height={1} overflow="hidden"><Text fg={quoteStatus.ended ? colors.warning : colors.negative}>{quoteStatus.text}</Text></Box>
|
|
436
465
|
)}
|
|
437
466
|
{navAsOf && !compactNavAsOf && (
|
|
438
467
|
<Box height={1} overflow="hidden"><Text fg={colors.textDim}>{navAsOf}</Text></Box>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { Box } from "../../../ui";
|
|
3
3
|
import { usePaneSettingValue, useShortcut } from "../../../public/react";
|
|
4
|
-
import { DataTableStackView, KeyValueRow, PaneStatusBody, StatGrid, usePaneFooter, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, usePaneTicker, type DataTableColumn } from "../../../components";
|
|
4
|
+
import { splitDelayedStatus, DataTableStackView, KeyValueRow, PaneStatusBody, StatGrid, usePaneFooter, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, usePaneTicker, type DataTableColumn } from "../../../components";
|
|
5
5
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
6
6
|
import type { PaneProps } from "../../../types/plugin";
|
|
7
7
|
import type { TapeQuote, TapeSnapshot, TapeTrade } from "../../../api-client/tape";
|
|
@@ -109,7 +109,7 @@ function TimeSalesView({ width, height, focused, symbol, exchange }: PaneProps &
|
|
|
109
109
|
usePaneNoticeFooter({ registrationId: "time-sales:notices", focused, notices: [...(data?.gaps ?? []), ...(resource.transport ? [resource.transport] : []),
|
|
110
110
|
...(pinnedBeforeTape ? [`The pinned moment is before this tape's first ${tab === "quotes" ? "quote" : "print"}, ${tapeStampNewYork(rowTime(rows.at(-1)!))}.`] : [])] });
|
|
111
111
|
usePaneStatusFooter({ registrationId: "time-sales:status", loading: resource.loading, error: resource.error,
|
|
112
|
-
info: data ? [{ id: "feed", quoteStatus: true, parts: [{ text: `${data.feed === "sip" ? "real-time" : "15m delayed"} · ${tapeStampNewYork(data.asOf)}`, tone: "muted" }] },
|
|
112
|
+
info: data ? [...splitDelayedStatus({ id: "feed", quoteStatus: true, parts: [{ text: `${data.feed === "sip" ? "real-time" : "15m delayed"} · ${tapeStampNewYork(data.asOf)}`, tone: "muted" }] }),
|
|
113
113
|
...(frozen ? [{ id: "paused", parts: [{ text: "paused", tone: "warning" as const }] }] : []),
|
|
114
114
|
...(!data.connected || resource.snapshotOnly ? [{ id: "snapshot", parts: [{ text: "snapshot", tone: "warning" as const }] }] : [])] : [] });
|
|
115
115
|
const selectTab = (value: string) => { setTab(value); setDetail(null); setSelected(null); };
|
|
@@ -4,6 +4,7 @@ import { signalWithTimeout } from "../../../utils/async-deadline";
|
|
|
4
4
|
import { parsePublicTickerKey } from "../../../utils/exchanges";
|
|
5
5
|
import { httpFetch } from "../../../utils/http-transport";
|
|
6
6
|
import { isoDateOrNull, numericOrNull, recordOrNull, textOrNull } from "../../../utils/guards";
|
|
7
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
7
8
|
import { zonedDateKey } from "../../../utils/zoned-date-time";
|
|
8
9
|
|
|
9
10
|
const ENDPOINT = "https://services-dynarep.ddwa.finra.org/public/reporting/v2/data/group/FixedIncomeMarket/name/corporateAndAgencySecurities";
|
|
@@ -226,6 +227,7 @@ function dataRows(data: unknown): unknown[] | null {
|
|
|
226
227
|
const parsed = JSON.parse(data) as unknown;
|
|
227
228
|
return Array.isArray(parsed) ? parsed : null;
|
|
228
229
|
} catch {
|
|
230
|
+
// Intentional ignore: malformed nested rows make postBondPage reject the response shape.
|
|
229
231
|
return null;
|
|
230
232
|
}
|
|
231
233
|
}
|
|
@@ -340,7 +342,9 @@ async function readPayload(response: Response): Promise<unknown> {
|
|
|
340
342
|
async function openSession(signal: AbortSignal): Promise<string> {
|
|
341
343
|
const session = await httpFetch(ENDPOINT, { headers: REQUEST_HEADERS, signal });
|
|
342
344
|
const token = xsrfToken(session);
|
|
343
|
-
await session.body?.cancel().catch(() => {
|
|
345
|
+
await session.body?.cancel().catch(() => {
|
|
346
|
+
// Intentional ignore: only session headers are needed; discarding its unused body is best effort.
|
|
347
|
+
});
|
|
344
348
|
if (!token) throw new Error(failureText(session.status, "session"));
|
|
345
349
|
return token;
|
|
346
350
|
}
|
|
@@ -477,7 +481,14 @@ export async function fetchBondTape(input: string, options: BondTapeOptions = {}
|
|
|
477
481
|
let answer = first;
|
|
478
482
|
let issuer: BondIssuer | null = null;
|
|
479
483
|
if (first.bonds.length === 0 && query?.kind === "issuer" && query.ticker && options.companyName) {
|
|
480
|
-
const company = await options.companyName(query.ticker).catch(() =>
|
|
484
|
+
const company = await options.companyName(query.ticker).catch((error: unknown) => {
|
|
485
|
+
options.signal?.throwIfAborted();
|
|
486
|
+
if (error instanceof Error && error.name === "AbortError") throw error;
|
|
487
|
+
logFailure("trace-bonds.client", "Resolve bond issuer company failed")(error);
|
|
488
|
+
answer = { ...first, failures: [...first.failures, "Issuer-name lookup failed."] };
|
|
489
|
+
return null;
|
|
490
|
+
});
|
|
491
|
+
options.signal?.throwIfAborted();
|
|
481
492
|
const name = company ? tapeIssuerName(company) : null;
|
|
482
493
|
if (company && name && name !== query.name) {
|
|
483
494
|
issuer = { company, name };
|
|
@@ -492,7 +503,7 @@ export async function fetchBondTape(input: string, options: BondTapeOptions = {}
|
|
|
492
503
|
// No prints for the query: date the answer by the market's newest sale.
|
|
493
504
|
const market = await postBondPage(token, [TRADED], 1, deadline)
|
|
494
505
|
.then((payload) => latestTradeDate(mergeBondAnswers([{ payload, accepts: anyBond, limit: 1 }]).bonds))
|
|
495
|
-
.catch((
|
|
506
|
+
.catch(logFailure("trace-bonds.client", "Load bond market date failed", null));
|
|
496
507
|
return { ...result, tradeDate: market };
|
|
497
508
|
}
|
|
498
509
|
|
|
@@ -89,7 +89,7 @@ export const traceBondsHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
89
89
|
rows: bonds.map(reportRow),
|
|
90
90
|
...(query ? { empty: noPrintsText(query, tradeDate, issuer) } : {}),
|
|
91
91
|
}],
|
|
92
|
-
complete:
|
|
92
|
+
complete: failures.length === 0,
|
|
93
93
|
...(failures.length > 0 ? { errors: failures } : {}),
|
|
94
94
|
...(truncated ? { notes: [TRUNCATED_TEXT] } : {}),
|
|
95
95
|
...(shown ? { warnings: [issuerFallbackText(query!, issuer)] } : {}),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { DataTableView, EmptyState, PaneStatusBody, QueryBar, StatGrid, statGridRows, usePaneFooter, usePaneMenuItems,
|
|
2
|
+
import { splitDelayedStatus, DataTableView, EmptyState, PaneStatusBody, QueryBar, StatGrid, statGridRows, usePaneFooter, usePaneMenuItems,
|
|
3
3
|
usePaneNoticeFooter, usePaneTabs, usePaneTicker, type DataTableColumn, type DataTableKeyEvent, type StatItem } from "../../../components";
|
|
4
4
|
import { useTableLoadMore } from "../../../components/table-view-shared";
|
|
5
5
|
import { useStaticChartBitmapSize } from "../../../components/chart/composite/bitmap";
|
|
@@ -360,7 +360,7 @@ export function VolSurfacePane({ focused, width, height }: PaneProps) {
|
|
|
360
360
|
info: [
|
|
361
361
|
...(active.loading ? [{ id: "loading", parts: [{ text: snapshot && snapshot.loaded < snapshot.requested
|
|
362
362
|
? `loading ${snapshot.loaded}/${snapshot.requested} expiries` : "loading", tone: "muted" as const }] }] : []),
|
|
363
|
-
...(freshness ?
|
|
363
|
+
...(freshness ? splitDelayedStatus({ id: "source", parts: [{ text: freshness, tone: "muted" }] }) : []),
|
|
364
364
|
...(snapshot?.expiries.some((entry) => entry.stale) ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []),
|
|
365
365
|
...(selectedExpiry?.fit && activeTab === "smile" ? [{ id: "fit", parts: [{ text: `${selectedExpiry.fit.method} · RMSE ${(selectedExpiry.fit.residual * 100).toFixed(3)} vol pts`, tone: "muted" as const }] }] : []),
|
|
366
366
|
...(selectionStatus ? [{ id: "selection", parts: [{ text: selectionStatus, tone: "muted" as const }] }] : []),
|
|
@@ -31,7 +31,7 @@ export const BUILTIN_EDITORIAL: Readonly<Record<string, BuiltinEditorial>> = {
|
|
|
31
31
|
tagline: "Account, chat, Ask Gloom and Cloud data.",
|
|
32
32
|
icon: "plugin-icons/gloomberb-cloud.webp",
|
|
33
33
|
},
|
|
34
|
-
alerts: { categories: ["alerts"], tagline: "Price,
|
|
34
|
+
alerts: { categories: ["alerts"], tagline: "Price, earnings, filing, news and flow alerts." },
|
|
35
35
|
"alt-data": {
|
|
36
36
|
categories: ["research", "data"],
|
|
37
37
|
tagline: "Supply chains, hiring, apps, GPUs and power.",
|
package/src/plugins/compat.ts
CHANGED
|
@@ -68,6 +68,8 @@ export interface PluginManifest {
|
|
|
68
68
|
id: string | null;
|
|
69
69
|
/** The oldest Gloomberb the checkout says it runs on. */
|
|
70
70
|
minGloomberb: string | null;
|
|
71
|
+
/** gloom.json `icon`: the plugin's logo, a path inside the checkout such as `icon.webp`. */
|
|
72
|
+
icon: string | null;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
function readJson(path: string): Record<string, unknown> | null {
|
|
@@ -106,6 +108,7 @@ export function readPluginManifest(pluginDir: string): PluginManifest {
|
|
|
106
108
|
minGloomberb: versionField(gloom?.minGloom)
|
|
107
109
|
?? versionField(gloom?.minGloomberb)
|
|
108
110
|
?? peerRangeMinimum(peers?.gloomberb),
|
|
111
|
+
icon: typeof gloom?.icon === "string" && gloom.icon.trim() ? gloom.icon.trim() : null,
|
|
109
112
|
};
|
|
110
113
|
}
|
|
111
114
|
|
|
@@ -24,6 +24,11 @@ export function listExternalPlugins(): readonly LoadedExternalPlugin[] {
|
|
|
24
24
|
return entries;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** The logo of a plugin loaded here, as a `data:` URL; null for a built-in or a plugin without one. */
|
|
28
|
+
export function externalPluginIcon(pluginId: string): string | null {
|
|
29
|
+
return entries.find((entry) => entry.plugin.id === pluginId)?.icon ?? null;
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
/** Replaces the entry for the same plugin id or directory, or appends. */
|
|
28
33
|
export function upsertExternalPlugin(entry: LoadedExternalPlugin): void {
|
|
29
34
|
const index = entries.findIndex((existing) => (
|
package/src/plugins/loader.ts
CHANGED
|
@@ -33,6 +33,12 @@ export interface LoadedExternalPlugin {
|
|
|
33
33
|
repo?: string;
|
|
34
34
|
/** A symlink to a local checkout (`gloomberb plugin link`) rather than a clone. */
|
|
35
35
|
linked?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* The plugin's logo as a `data:` URL (plugin-icon.ts), for the desktop and
|
|
38
|
+
* the web to draw beside what it contributes, such as its broker. The
|
|
39
|
+
* terminal draws no images and leaves it unread.
|
|
40
|
+
*/
|
|
41
|
+
icon?: string;
|
|
36
42
|
error?: string;
|
|
37
43
|
/**
|
|
38
44
|
* The Gloomberb the checkout declares it needs, when this one is older. The
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync, readFileSync, realpathSync, statSync } from "fs";
|
|
2
|
+
import { basename, extname, isAbsolute, relative, resolve, sep } from "path";
|
|
3
|
+
import { logFailure } from "../utils/log-failure";
|
|
4
|
+
import { readPluginManifest } from "./compat";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A plugin's logo as the desktop and the web draw it: the file its gloom.json
|
|
8
|
+
* names as `icon` (PLUGINS.md), or the `icon.svg`, `icon.webp` or `icon.png`
|
|
9
|
+
* a checkout without one ships beside its entry, read into a `data:` URL. The
|
|
10
|
+
* view has no filesystem, so the Bun process that reads the plugins folder
|
|
11
|
+
* hands the image over with the plugin's bundle.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const ICON_TYPES: Readonly<Record<string, string>> = {
|
|
15
|
+
".svg": "image/svg+xml",
|
|
16
|
+
".webp": "image/webp",
|
|
17
|
+
".png": "image/png",
|
|
18
|
+
".jpg": "image/jpeg",
|
|
19
|
+
".jpeg": "image/jpeg",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const CONVENTIONAL_ICONS = ["icon.svg", "icon.webp", "icon.png"];
|
|
23
|
+
|
|
24
|
+
/** A logo is drawn at most a few dozen pixels wide; anything larger is not one. */
|
|
25
|
+
const MAX_ICON_BYTES = 256 * 1024;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The icon `name` names inside `pluginDir`, typed by the name the plugin gave
|
|
29
|
+
* it, or null for a path that leaves the checkout or is not a small image.
|
|
30
|
+
*/
|
|
31
|
+
function iconFile(pluginDir: string, name: string): { path: string; type: string } | null {
|
|
32
|
+
const type = ICON_TYPES[extname(name).toLowerCase()];
|
|
33
|
+
if (!type) return null;
|
|
34
|
+
const root = realpathSync(pluginDir);
|
|
35
|
+
const path = resolve(root, name);
|
|
36
|
+
if (!existsSync(path)) return null;
|
|
37
|
+
// A link that points out of the checkout is not the plugin's to show.
|
|
38
|
+
const real = realpathSync(path);
|
|
39
|
+
const inside = relative(root, real);
|
|
40
|
+
if (!inside || inside === ".." || inside.startsWith(`..${sep}`) || isAbsolute(inside)) return null;
|
|
41
|
+
const info = statSync(real);
|
|
42
|
+
return info.isFile() && info.size > 0 && info.size <= MAX_ICON_BYTES ? { path: real, type } : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The plugin's logo as a `data:` URL. A declared icon that is missing,
|
|
47
|
+
* unusable or unreadable falls back to the conventional names, each tried on
|
|
48
|
+
* its own, so one file the process may not read does not hide the next.
|
|
49
|
+
*/
|
|
50
|
+
export function readPluginIcon(pluginDir: string): string | null {
|
|
51
|
+
const declared = readPluginManifest(pluginDir).icon;
|
|
52
|
+
for (const name of declared ? [declared, ...CONVENTIONAL_ICONS] : CONVENTIONAL_ICONS) {
|
|
53
|
+
try {
|
|
54
|
+
const icon = iconFile(pluginDir, name);
|
|
55
|
+
if (icon) return `data:${icon.type};base64,${readFileSync(icon.path).toString("base64")}`;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
logFailure("plugin-icon", `Reading ${name} for ${basename(pluginDir)} failed`)(error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
@@ -9,6 +9,7 @@ import type { NewsQuery, NewsQueryState } from "../../types/news-source";
|
|
|
9
9
|
import type {
|
|
10
10
|
AppNotificationDelivery,
|
|
11
11
|
AppNotificationRequest,
|
|
12
|
+
BrokerInstanceCreateOptions,
|
|
12
13
|
BrokerInstanceUpdateOptions,
|
|
13
14
|
PaneSettingField,
|
|
14
15
|
PaneTemplateCreateOptions,
|
|
@@ -27,7 +28,7 @@ export interface PluginHostActions {
|
|
|
27
28
|
getTicker(symbol: string): TickerRecord | null;
|
|
28
29
|
getData(symbol: string): TickerFinancials | null;
|
|
29
30
|
getConfig(): AppConfig;
|
|
30
|
-
createBrokerInstance(brokerType: string, label: string, values: Record<string, unknown
|
|
31
|
+
createBrokerInstance(brokerType: string, label: string, values: Record<string, unknown>, options?: BrokerInstanceCreateOptions): Promise<BrokerInstanceConfig>;
|
|
31
32
|
connectBrokerInstance(instanceId: string): Promise<void>;
|
|
32
33
|
updateBrokerInstance(instanceId: string, values: Record<string, unknown>, options?: BrokerInstanceUpdateOptions): Promise<void>;
|
|
33
34
|
syncBrokerInstance(instanceId: string): Promise<void>;
|
|
@@ -45,6 +45,7 @@ import type {
|
|
|
45
45
|
import { debugLog } from "../../utils/debug-log";
|
|
46
46
|
import { logFailure } from "../../utils/log-failure";
|
|
47
47
|
import { EventBus, type HostEvents } from "../event-bus";
|
|
48
|
+
import { externalPluginIcon } from "../external-runtime";
|
|
48
49
|
import { isReservedBuiltinPluginId } from "../ownership";
|
|
49
50
|
import { createPluginPersistence } from "../plugin-persistence";
|
|
50
51
|
import { createPluginTeamState } from "../team-state";
|
|
@@ -532,6 +533,12 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
532
533
|
return this.contributions.brokersMap.owners.get(brokerType);
|
|
533
534
|
}
|
|
534
535
|
|
|
536
|
+
/** The logo of the plugin that registered `brokerType` (its gloom.json `icon`), for lists of brokers. */
|
|
537
|
+
getBrokerLogo = (brokerType: string): string | null => {
|
|
538
|
+
const owner = this.getBrokerPluginId(brokerType);
|
|
539
|
+
return owner ? externalPluginIcon(owner) : null;
|
|
540
|
+
};
|
|
541
|
+
|
|
535
542
|
getShortcutPluginId(shortcutId: string): string | undefined {
|
|
536
543
|
return this.contributions.shortcutsMap.owners.get(shortcutId);
|
|
537
544
|
}
|
|
@@ -619,6 +626,7 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
619
626
|
getData: (ticker) => this.getData(ticker),
|
|
620
627
|
getTicker: (symbol) => this.getTicker(symbol),
|
|
621
628
|
getConfig: () => this.getConfig(),
|
|
629
|
+
getBrokerAdapter: this.getBrokerAdapter,
|
|
622
630
|
getPaneDef: (paneId) => contributions.panesMap.get(paneId),
|
|
623
631
|
marketData: this.marketData,
|
|
624
632
|
connectionHealth: this.connectionHealth,
|
|
@@ -630,7 +638,7 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
630
638
|
updateLayout: (layout) => this.updateLayout(layout),
|
|
631
639
|
resolvePaneTarget: (paneId) => this.resolvePaneTarget(paneId),
|
|
632
640
|
}),
|
|
633
|
-
createBrokerInstance: (brokerType, label, values) => this.createBrokerInstance(brokerType, label, values),
|
|
641
|
+
createBrokerInstance: (brokerType, label, values, options) => this.createBrokerInstance(brokerType, label, values, options),
|
|
634
642
|
updateBrokerInstance: this.updateBrokerInstance,
|
|
635
643
|
syncBrokerInstance: this.syncBrokerInstance,
|
|
636
644
|
removeBrokerInstance: this.removeBrokerInstance,
|
|
@@ -18,6 +18,11 @@ export interface PluginRuntimeAccess extends CapabilityInvoker, PluginRuntimeHos
|
|
|
18
18
|
getBrokerAdapter(brokerType: string): BrokerAdapter | null;
|
|
19
19
|
/** Every broker adapter installed now, read when called: plugins come and go. */
|
|
20
20
|
listBrokerAdapters(): BrokerAdapter[];
|
|
21
|
+
/**
|
|
22
|
+
* The logo of the plugin that registered a broker adapter, as an image URL
|
|
23
|
+
* for the desktop and the web; null when it has none.
|
|
24
|
+
*/
|
|
25
|
+
getBrokerLogo?(brokerType: string): string | null;
|
|
21
26
|
subscribeResumeState(pluginId: string, key: string, listener: () => void): () => void;
|
|
22
27
|
getResumeState<T = unknown>(pluginId: string, key: string, schemaVersion?: number): T | null;
|
|
23
28
|
setResumeState(pluginId: string, key: string, value: unknown, schemaVersion?: number): void;
|
|
@@ -78,11 +78,14 @@ export function useCapabilityInvoker(): CapabilityInvoker {
|
|
|
78
78
|
return usePluginRenderContext().runtime;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
const noBrokerLogo = (): string | null => null;
|
|
82
|
+
|
|
81
83
|
export function usePluginBrokerActions() {
|
|
82
84
|
const { runtime } = usePluginRenderContext();
|
|
83
85
|
return {
|
|
84
86
|
getBrokerAdapter: runtime.getBrokerAdapter,
|
|
85
87
|
listBrokerAdapters: runtime.listBrokerAdapters,
|
|
88
|
+
getBrokerLogo: runtime.getBrokerLogo ?? noBrokerLogo,
|
|
86
89
|
createBrokerInstance: runtime.createBrokerInstance,
|
|
87
90
|
connectBrokerInstance: runtime.connectBrokerInstance,
|
|
88
91
|
updateBrokerInstance: runtime.updateBrokerInstance,
|
|
@@ -8,6 +8,7 @@ import type { EarningsEvent } from "../types/data-provider";
|
|
|
8
8
|
import type { TickerRecord } from "../types/ticker";
|
|
9
9
|
import { colors, priceColor } from "../theme/colors";
|
|
10
10
|
import { formatQuoteAgeWithSource, resolveQuoteAgeTimestamp } from "../market-data/quotes/time";
|
|
11
|
+
import { delistingStateLabel } from "../market-data/quotes/delisting";
|
|
11
12
|
import { formatFeedTime, formatShortDate, parseDisplayDate, type ShortDateOptions } from "../utils/datetime-format";
|
|
12
13
|
import { convertCurrency, formatCompact, formatCompactAmount, formatNumber, formatPercentRaw } from "../utils/format";
|
|
13
14
|
import {
|
|
@@ -40,6 +41,7 @@ import { currentListingName } from "../tickers/listing-name";
|
|
|
40
41
|
import { PRICE_SPARKLINE_COLUMN_ID } from "../components/price-sparkline/view";
|
|
41
42
|
import { followLiveSparklinePrice, resolveSparklineHistory, sparklineValues } from "../components/price-sparkline/model";
|
|
42
43
|
import {
|
|
44
|
+
analystTargetDisplayCurrency,
|
|
43
45
|
listingDividendYield,
|
|
44
46
|
liveDividendYield,
|
|
45
47
|
liveFiftyTwoWeekRange,
|
|
@@ -67,6 +69,7 @@ import {
|
|
|
67
69
|
} from "./valuation-basis";
|
|
68
70
|
import { futuresQuantityText, futuresSettlementDisplay, type PortfolioFuturesTerms } from "./futures-positions";
|
|
69
71
|
import { isFiniteNumber } from "../utils/guards";
|
|
72
|
+
import { currencyCodeLabel } from "../utils/currency-units";
|
|
70
73
|
import {
|
|
71
74
|
allocationFigures,
|
|
72
75
|
formatAllocationDrift,
|
|
@@ -453,7 +456,7 @@ export function getColumnValue(
|
|
|
453
456
|
),
|
|
454
457
|
};
|
|
455
458
|
case "currency":
|
|
456
|
-
return { text: compactText(listedCurrency(positionCurrency, quoteCurrency, ticker, quote)) };
|
|
459
|
+
return { text: compactText(currencyCodeLabel(listedCurrency(positionCurrency, quoteCurrency, ticker, quote))) };
|
|
457
460
|
case "sector":
|
|
458
461
|
return { text: compactText(ticker.metadata.sector || financials?.profile?.sector) };
|
|
459
462
|
case "industry":
|
|
@@ -464,6 +467,8 @@ export function getColumnValue(
|
|
|
464
467
|
return resolvePortfolioPriceValue(headlineQuote, brokerMarkPrice, headlineQuote ? currentQuoteOptions : markOptions, col.width, quote?.marketState);
|
|
465
468
|
case "change":
|
|
466
469
|
if (!headlineQuote) return { text: "—" };
|
|
470
|
+
// A listing that no longer trades has no day's move: its last session's is years old.
|
|
471
|
+
if (quote?.delisting) return { text: delistingStateLabel(quote.delisting), color: colors.warning };
|
|
467
472
|
return {
|
|
468
473
|
text: formatSignedMarketPrice(headlineQuote.change, { ...currentQuoteOptions, maxWidth: col.width }),
|
|
469
474
|
color: marketChangeColor(headlineQuote.change, quote?.marketState),
|
|
@@ -489,6 +494,7 @@ export function getColumnValue(
|
|
|
489
494
|
return { text: `${formatCompact(quote?.bidSize)}/${formatCompact(quote?.askSize)}` };
|
|
490
495
|
}
|
|
491
496
|
case "change_pct": {
|
|
497
|
+
if (quote?.delisting) return { text: delistingStateLabel(quote.delisting), color: colors.warning };
|
|
492
498
|
const changePercent = headlineChangePercent(headlineQuote, quote);
|
|
493
499
|
return quote
|
|
494
500
|
? { text: formatPercentRaw(changePercent), color: marketChangeColor(changePercent, quote.marketState) }
|
|
@@ -573,6 +579,7 @@ export function getColumnValue(
|
|
|
573
579
|
return { text, color: /[1-9]/.test(text) ? priceColor(allocation.tradeValue!) : undefined };
|
|
574
580
|
}
|
|
575
581
|
case "day_pnl":
|
|
582
|
+
if (quote?.delisting) return { text: delistingStateLabel(quote.delisting), color: colors.warning };
|
|
576
583
|
if (activeQuote && isFiniteNumber(activeQuote.change) && Number.isFinite(positionMetrics.grossPriceUnits) && positionMetrics.grossPriceUnits !== 0) {
|
|
577
584
|
const dayPnl = toBaseQuote(totalPriceUnits * activeQuote.change);
|
|
578
585
|
return { text: formatCompactAmount(dayPnl, { signed: true }), color: priceColor(dayPnl) };
|
|
@@ -603,7 +610,8 @@ export function getColumnValue(
|
|
|
603
610
|
const analyst = mapData(ctx.analystResearch, ticker.metadata.ticker);
|
|
604
611
|
if (analyst.pending) return { text: "…" };
|
|
605
612
|
const value = targetValue(analyst.data);
|
|
606
|
-
|
|
613
|
+
// The target's own currency: one in pounds under a quote in pence reads £, not p.
|
|
614
|
+
const currency = analystTargetDisplayCurrency(analyst.data, quoteCurrency, displayQuote?.price);
|
|
607
615
|
return {
|
|
608
616
|
text: value == null
|
|
609
617
|
? "—"
|
|
@@ -730,9 +738,9 @@ export function getSortValue(
|
|
|
730
738
|
? (quote?.bidSize ?? 0) + (quote?.askSize ?? 0)
|
|
731
739
|
: null;
|
|
732
740
|
case "change":
|
|
733
|
-
return headlineQuote?.change ?? null;
|
|
741
|
+
return quote?.delisting ? null : headlineQuote?.change ?? null;
|
|
734
742
|
case "change_pct":
|
|
735
|
-
return headlineChangePercent(headlineQuote, quote) ?? null;
|
|
743
|
+
return quote?.delisting ? null : headlineChangePercent(headlineQuote, quote) ?? null;
|
|
736
744
|
case "volume":
|
|
737
745
|
return reportedQuoteVolume(quote);
|
|
738
746
|
case "dollar_volume": {
|
|
@@ -774,6 +782,7 @@ export function getSortValue(
|
|
|
774
782
|
case "trade_value":
|
|
775
783
|
return rowAllocation(ticker, activeQuote, baseMetrics, toBaseQuote, ctx).tradeValue;
|
|
776
784
|
case "day_pnl":
|
|
785
|
+
if (quote?.delisting) return null;
|
|
777
786
|
if (activeQuote && isFiniteNumber(activeQuote.change) && Number.isFinite(positionMetrics.grossPriceUnits) && positionMetrics.grossPriceUnits !== 0) {
|
|
778
787
|
return toBaseQuote(totalPriceUnits * activeQuote.change);
|
|
779
788
|
}
|