gloomberb 0.18.0 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api-client/commodity-balances.ts +6 -0
- package/src/api-client/company-kpis.ts +9 -0
- package/src/api-client/crypto-markets.ts +9 -0
- package/src/api-client/data.ts +6 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/types.ts +14 -0
- package/src/app/pane-runtime/plugin-bindings.ts +3 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
- package/src/app/runtime/plugin-bindings.ts +6 -2
- package/src/brokers/logos.ts +66 -0
- package/src/brokers/profile-form.ts +3 -0
- package/src/brokers/remote-broker-adapter.ts +13 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/service-capabilities.ts +7 -1
- package/src/cli/base-converter.ts +60 -18
- package/src/cli/commands/fred-series.ts +15 -5
- package/src/cli/commands/market.ts +290 -159
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/overview.ts +31 -6
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +105 -52
- package/src/cli/currency-hints.ts +27 -0
- package/src/cli/fx-pair.ts +41 -4
- package/src/cli/glossary.ts +57 -4
- package/src/cli/helpers.ts +36 -3
- package/src/cli/index.ts +4 -1
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +37 -2
- package/src/cli/pane-functions/catalog.ts +3 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +9 -4
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +31 -7
- package/src/cli/pane-functions/headless.ts +6 -5
- package/src/cli/pane-functions/index.ts +3 -2
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +2 -1
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/series-search.ts +2 -1
- package/src/cli/symbol-list.ts +51 -16
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +22 -7
- package/src/components/chart/composite/format.ts +13 -1
- package/src/components/chart/composite/level-labels.ts +30 -0
- package/src/components/chart/composite/levels.ts +17 -6
- package/src/components/chart/composite/panel-surface.tsx +64 -16
- package/src/components/chart/composite/price-axis-labels.tsx +2 -1
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/command-bar/workflow/collection-actions.ts +4 -0
- package/src/components/index.ts +2 -0
- package/src/components/layout/pane/content.tsx +16 -14
- package/src/components/layout/pane/footer/index.tsx +22 -3
- package/src/components/layout/pane/footer/model.ts +99 -4
- package/src/components/layout/pane/header-tabs.tsx +15 -1
- package/src/components/onboarding/onboarding-frame.tsx +9 -11
- package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
- package/src/components/onboarding/wizard-model.ts +10 -3
- package/src/components/onboarding/wizard-positions.ts +8 -0
- package/src/components/ui/button.tsx +11 -4
- package/src/components/ui/choice-dialog.tsx +8 -5
- package/src/components/ui/display.tsx +11 -6
- package/src/components/ui/fields.tsx +100 -20
- package/src/components/ui/icon.tsx +6 -1
- package/src/components/ui/list-row-lines.tsx +87 -0
- package/src/components/ui/list-view.tsx +14 -0
- package/src/components/ui/logo-mark.tsx +64 -0
- package/src/components/ui/number-step.ts +54 -0
- package/src/components/ui/select-button.tsx +5 -4
- package/src/components/ui/select-field.tsx +3 -3
- package/src/components/ui/status.tsx +17 -3
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- package/src/core/state/app/instrument.ts +8 -3
- package/src/data/config/store/normalize.ts +4 -1
- package/src/data/plugin-cache.ts +32 -6
- package/src/docs/snapshot.json +57 -22
- package/src/i18n/es.ts +9 -3
- package/src/i18n/ja.ts +9 -3
- package/src/i18n/ko.ts +9 -3
- package/src/i18n/zh-cn.ts +10 -4
- package/src/i18n/zh-tw.ts +10 -4
- package/src/market-data/commodity-names.ts +80 -0
- package/src/market-data/coordinator/auxiliary.ts +3 -1
- package/src/market-data/coordinator/chart.ts +3 -1
- package/src/market-data/futures-board.ts +38 -1
- package/src/market-data/futures-contract.ts +91 -0
- package/src/market-data/fx-closes.ts +78 -1
- package/src/market-data/market/change-basis.ts +219 -0
- package/src/market-data/market/format.ts +28 -12
- package/src/market-data/market/freshness.ts +38 -0
- package/src/market-data/market/status.ts +55 -9
- package/src/market-data/market/year-to-date.ts +46 -0
- package/src/market-data/quotes/contributions.ts +12 -0
- package/src/market-data/quotes/delisting.ts +34 -0
- package/src/market-data/quotes/live-valuation.ts +23 -0
- package/src/market-data/quotes/resolution.ts +8 -2
- package/src/market-data/tax-equivalent.ts +10 -2
- package/src/notifications/app-notifier.ts +2 -2
- package/src/notifications/notification-log.ts +8 -6
- package/src/plugins/builtin/alerts/cli.ts +26 -18
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/analytics/index.tsx +1 -1
- package/src/plugins/builtin/analytics/risk-client.ts +44 -11
- package/src/plugins/builtin/analytics/risk-fx.ts +61 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +10 -1
- package/src/plugins/builtin/analytics/risk-model.ts +79 -16
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/analytics/risk-test-data.ts +3 -0
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- package/src/plugins/builtin/backtest/window.ts +16 -3
- package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
- package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
- package/src/plugins/builtin/broker-manager/index.tsx +20 -9
- package/src/plugins/builtin/broker-manager/model.ts +5 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
- package/src/plugins/builtin/broker-manager/table.ts +5 -1
- package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
- package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
- package/src/plugins/builtin/broker-trading/command.ts +103 -0
- package/src/plugins/builtin/broker-trading/controller.ts +688 -0
- package/src/plugins/builtin/broker-trading/dock.tsx +182 -0
- package/src/plugins/builtin/broker-trading/headless.ts +76 -0
- package/src/plugins/builtin/broker-trading/index.tsx +77 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
- package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
- package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
- package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
- package/src/plugins/builtin/broker-trading/pane.tsx +505 -0
- package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
- package/src/plugins/builtin/broker-trading/ticket.tsx +573 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
- package/src/plugins/builtin/buildout/format.ts +1 -0
- package/src/plugins/builtin/buildout/model/index.ts +16 -7
- package/src/plugins/builtin/buildout/pane/index.tsx +3 -2
- package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
- package/src/plugins/builtin/chart-composer/headless.ts +2 -1
- package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +31 -11
- package/src/plugins/builtin/chart-composer/presets.ts +2 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
- package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
- package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
- package/src/plugins/builtin/cloud/team/pane-sections.tsx +10 -3
- package/src/plugins/builtin/cloud/team/pane.tsx +30 -9
- package/src/plugins/builtin/commodity-balances/client.ts +6 -4
- package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
- package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
- package/src/plugins/builtin/commodity-balances/model.ts +22 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +36 -5
- package/src/plugins/builtin/company-kpis/client.ts +3 -2
- package/src/plugins/builtin/company-kpis/headless.ts +5 -7
- package/src/plugins/builtin/company-kpis/model.ts +85 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +26 -12
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cot/index.tsx +1 -1
- package/src/plugins/builtin/cot/model.ts +35 -12
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
- package/src/plugins/builtin/crypto-board/client.ts +33 -2
- package/src/plugins/builtin/crypto-board/headless.ts +5 -2
- package/src/plugins/builtin/crypto-board/model.ts +28 -8
- package/src/plugins/builtin/custom-view/loader.ts +3 -2
- package/src/plugins/builtin/custom-view/pane.tsx +5 -4
- package/src/plugins/builtin/custom-view/view-spec.ts +1 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
- package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
- package/src/plugins/builtin/econ/calendar-model.ts +38 -2
- package/src/plugins/builtin/econ/fred-catalog.ts +6 -2
- package/src/plugins/builtin/econ/fred-series-map.ts +12 -1
- package/src/plugins/builtin/econ/index.tsx +31 -4
- package/src/plugins/builtin/econ/print-watch.ts +279 -0
- package/src/plugins/builtin/equity-screener/headless.ts +20 -5
- package/src/plugins/builtin/equity-screener/model.ts +40 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +24 -21
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
- package/src/plugins/builtin/executives/data.ts +24 -8
- package/src/plugins/builtin/executives/index.tsx +2 -18
- package/src/plugins/builtin/executives/pane.tsx +34 -10
- package/src/plugins/builtin/exposure/client.ts +4 -1
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/exposure/evidence.ts +4 -1
- package/src/plugins/builtin/exposure/holdings.ts +3 -1
- package/src/plugins/builtin/exposure/pane.tsx +37 -16
- package/src/plugins/builtin/filing-events/index.tsx +9 -3
- package/src/plugins/builtin/futures/table.tsx +6 -3
- package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
- package/src/plugins/builtin/insider/base-values.ts +72 -0
- package/src/plugins/builtin/insider/headless.ts +29 -4
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- package/src/plugins/builtin/market-halts/model.ts +31 -6
- package/src/plugins/builtin/market-halts/pane.tsx +3 -0
- package/src/plugins/builtin/market-movers/client.ts +2 -2
- package/src/plugins/builtin/market-movers/index.tsx +0 -3
- package/src/plugins/builtin/market-movers/session-body.tsx +27 -11
- package/src/plugins/builtin/market-movers/session.ts +25 -10
- package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +33 -13
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/letters/client.ts +1 -0
- package/src/plugins/builtin/news/wire/news/command-bar-search.ts +6 -3
- package/src/plugins/builtin/news/wire/news/detail-view.tsx +2 -1
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -1
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/feed-xml.ts +6 -1
- package/src/plugins/builtin/news/wire/rss/managed-feed.ts +1 -0
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/notes/cli.ts +33 -8
- package/src/plugins/builtin/notes/export.ts +49 -3
- package/src/plugins/builtin/notes/files.ts +14 -3
- package/src/plugins/builtin/notes/index.tsx +3 -3
- package/src/plugins/builtin/notes/quick-notes-pane.tsx +45 -10
- package/src/plugins/builtin/notes/store.ts +13 -3
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/view.tsx +77 -22
- package/src/plugins/builtin/options-calculator/pane.tsx +4 -2
- package/src/plugins/builtin/options-scenario/pane.tsx +41 -6
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -7
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +166 -15
- package/src/plugins/builtin/portfolio-list/cli/render.ts +69 -15
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +2 -2
- package/src/plugins/builtin/portfolio-list/headless.ts +3 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +44 -17
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -1
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +42 -7
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- package/src/plugins/builtin/primary-dealers/client.ts +137 -62
- package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
- package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
- package/src/plugins/builtin/primary-dealers/model.ts +179 -129
- package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
- package/src/plugins/builtin/research/analyst-model.ts +5 -1
- package/src/plugins/builtin/risk-factors/index.tsx +12 -3
- package/src/plugins/builtin/scanner/feed.ts +6 -1
- package/src/plugins/builtin/scanner/headless.ts +1 -1
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/broker-reconnect.ts +105 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
- package/src/plugins/builtin/shared/free-routes.ts +24 -0
- package/src/plugins/builtin/shared/headless-options.ts +11 -0
- package/src/plugins/builtin/shared/pro-feature-wall.tsx +9 -4
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +62 -0
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -2
- package/src/plugins/builtin/themes/model.ts +43 -10
- package/src/plugins/builtin/themes/pane.tsx +8 -6
- package/src/plugins/builtin/thirteenf/api.ts +4 -1
- package/src/plugins/builtin/thirteenf/data.ts +5 -1
- package/src/plugins/builtin/thirteenf/headless.ts +3 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +8 -3
- package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
- package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
- package/src/plugins/builtin/time-sales/pane.tsx +2 -2
- package/src/plugins/builtin/trace-bonds/client.ts +14 -3
- package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +2 -2
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/compat.ts +3 -0
- package/src/plugins/external-runtime.ts +5 -0
- package/src/plugins/loader.ts +6 -0
- package/src/plugins/plugin-icon.ts +61 -0
- package/src/plugins/registry/host-actions.ts +2 -1
- package/src/plugins/registry/index.ts +9 -1
- package/src/plugins/runtime/context.tsx +5 -0
- package/src/plugins/runtime/index.tsx +3 -0
- package/src/portfolio/column-values.ts +13 -4
- package/src/portfolio/cost-fx.ts +61 -0
- package/src/portfolio/mutations.ts +16 -2
- package/src/portfolio/position-metrics.ts +45 -4
- package/src/portfolio/position-quantity.ts +43 -0
- package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
- package/src/public/broker.ts +8 -0
- package/src/public/market-data.ts +5 -0
- package/src/renderers/dom/desktop/control-styles.ts +7 -1
- package/src/renderers/dom/desktop/controls.tsx +39 -7
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/list-view.tsx +51 -19
- package/src/renderers/dom/desktop/menu.tsx +10 -1
- package/src/renderers/dom/desktop/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +6 -0
- package/src/renderers/dom/styles.css +12 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +12 -0
- package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
- package/src/renderers/electrobun/view/external-plugins.ts +1 -0
- package/src/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +27 -2
- package/src/sources/gloomberb-cloud/index.ts +50 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/provider-errors.ts +3 -1
- package/src/sources/provider-router/history.ts +49 -6
- package/src/sources/provider-router/index.ts +2 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +63 -11
- package/src/sync/local-only.ts +121 -0
- package/src/sync/react.ts +6 -0
- package/src/sync/types.ts +3 -0
- package/src/team/collections.ts +2 -0
- package/src/tickers/listing-fate.ts +27 -8
- package/src/tickers/metadata.ts +10 -0
- package/src/tickers/search/index.ts +24 -3
- package/src/time-series/price-comparison.ts +35 -0
- package/src/time-series/resolve.ts +52 -11
- package/src/time-series/spec.ts +1 -1
- package/src/time-series/types.ts +5 -0
- package/src/types/broker.ts +56 -1
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +5 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +12 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/ticker.ts +9 -0
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +2 -1
- package/src/utils/currency-units.ts +41 -1
- package/src/utils/dom-screenshot.ts +22 -9
- package/src/utils/exchange-notation.generated.ts +360 -0
- package/src/utils/exchanges.ts +15 -322
- package/src/utils/format.ts +31 -1
- package/src/utils/instrument-identity.ts +38 -1
- package/src/utils/options.ts +46 -1
- package/src/utils/price-history.ts +4 -0
- package/src/utils/reported-money.ts +8 -4
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { apiClient } from "../../../../api-client";
|
|
2
2
|
import { httpFetch } from "../../../../utils/http-transport";
|
|
3
|
+
import { logFailure } from "../../../../utils/log-failure";
|
|
3
4
|
import type {
|
|
4
5
|
BuildoutAccess,
|
|
5
6
|
BuildoutCompaniesPayload,
|
|
@@ -55,7 +56,10 @@ export async function buildoutApi<T>(path: string, token: string | null, init: R
|
|
|
55
56
|
if (token) headers.set("Authorization", `Bearer ${token}`);
|
|
56
57
|
const response = await httpFetch(`${BUILDOUT_API_URL}${path}`, { ...init, headers });
|
|
57
58
|
if (!response.ok) {
|
|
58
|
-
const body = await response.text().catch(() =>
|
|
59
|
+
const body = await response.text().catch(() => {
|
|
60
|
+
// Intentional ignore: the failed request still throws its status when the error body is unreadable.
|
|
61
|
+
return "";
|
|
62
|
+
});
|
|
59
63
|
throw new Error(body || `${BUILDOUT_NAME} request failed (${response.status})`);
|
|
60
64
|
}
|
|
61
65
|
return response.json() as Promise<T>;
|
|
@@ -124,12 +128,17 @@ export async function loadBuildoutData(token: string | null) {
|
|
|
124
128
|
export async function getBuildoutProToken() {
|
|
125
129
|
if (!apiClient.isSignedIn()) return null;
|
|
126
130
|
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
try {
|
|
132
|
+
const session = await apiClient.getSession();
|
|
133
|
+
if (!session) return null;
|
|
129
134
|
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
const account = await apiClient.getBuildoutAccount();
|
|
136
|
+
if (!account?.subscription.active) return null;
|
|
132
137
|
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
const token = await apiClient.getBuildoutToken();
|
|
139
|
+
return token?.token ?? null;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
logFailure("buildout.model", "Check access failed")(error);
|
|
142
|
+
throw new Error("Access check failed. Access is unknown.");
|
|
143
|
+
}
|
|
135
144
|
}
|
|
@@ -16,6 +16,7 @@ import { handleRefreshKey } from "../../../../components/data-table/table-pane";
|
|
|
16
16
|
import type { PaneProps } from "../../../../types/plugin";
|
|
17
17
|
import { useOptionalDialog, type PromptContext } from "../../../../ui/dialog";
|
|
18
18
|
import { isPlainKey } from "../../../../utils/keyboard";
|
|
19
|
+
import { logFailure } from "../../../../utils/log-failure";
|
|
19
20
|
import { nextHeaderSort, type SortPreference } from "../../../../utils/sort-values";
|
|
20
21
|
import { usePaneStateValue } from "../../../../state/app/context";
|
|
21
22
|
import { useInlineTickerQuoteFact, useInlineTickers } from "../../../../state/hooks/inline-tickers";
|
|
@@ -201,7 +202,7 @@ export function BuildoutPane({ focused, width, height }: PaneProps) {
|
|
|
201
202
|
choices={openTargets.map((target) => ({ id: target.id, label: target.label, detail: target.detail }))}
|
|
202
203
|
/>
|
|
203
204
|
),
|
|
204
|
-
}).catch((
|
|
205
|
+
}).catch(logFailure("buildout.pane", "Choose open target failed"));
|
|
205
206
|
const target = openTargets.find((entry) => entry.id === chosen);
|
|
206
207
|
if (target) openTarget(target);
|
|
207
208
|
}, [dialog, openTarget, openTargets]);
|
|
@@ -383,7 +384,7 @@ export function BuildoutPane({ focused, width, height }: PaneProps) {
|
|
|
383
384
|
}
|
|
384
385
|
|
|
385
386
|
if (state.status === "error") {
|
|
386
|
-
return <PaneStatusBody error={state.message} subject={BUILDOUT_NAME} width={width} height={height} />;
|
|
387
|
+
return <PaneStatusBody error={state.message} subject={BUILDOUT_NAME} width={width} height={height} retry={{ onRetry: refresh }} />;
|
|
387
388
|
}
|
|
388
389
|
|
|
389
390
|
const listOpen = activeTab === "companies" && !!selectedList;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PaneStatusBody, type PaneFooterSegment } from "../../../../components";
|
|
1
|
+
import { delayedFooterSegment, splitDelayedStatus, PaneStatusBody, type PaneFooterSegment } from "../../../../components";
|
|
2
2
|
import type {
|
|
3
3
|
BuildoutList,
|
|
4
4
|
BuildoutLoadState,
|
|
@@ -44,10 +44,10 @@ export function updateBuildoutFooterInfo(
|
|
|
44
44
|
|
|
45
45
|
const info: PaneFooterSegment[] = [];
|
|
46
46
|
if (messages.quoteFreshness) {
|
|
47
|
-
info.push({ id: "quote", parts: [{ text: messages.quoteFreshness, tone: "muted" }] });
|
|
47
|
+
info.push(...splitDelayedStatus({ id: "quote", parts: [{ text: messages.quoteFreshness, tone: "muted" }] }));
|
|
48
48
|
}
|
|
49
49
|
if (state.access !== "pro" && activeTab === "intel") {
|
|
50
|
-
info.push({ id: "access",
|
|
50
|
+
info.push(delayedFooterSegment({ id: "access", detail: "72h delayed", delay: "72h", onPress: messages.onUpgrade }));
|
|
51
51
|
}
|
|
52
52
|
if (messages.partialList) {
|
|
53
53
|
info.push({ id: "partial", onPress: messages.onUpgrade, parts: [{ text: "partial list", tone: "warning" }] });
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
normalizeChartSpec,
|
|
11
11
|
} from "../../../time-series/spec";
|
|
12
12
|
import type { ChartSeriesSpec, ChartSpec, SeriesPeriod } from "../../../time-series/types";
|
|
13
|
+
import { yearToDateBaseDay } from "../../../market-data/market/year-to-date";
|
|
13
14
|
|
|
14
15
|
const PRICE_CAPABILITIES = new Set(["price-chart", "intraday-price-chart", "price-comparison"]);
|
|
15
16
|
|
|
@@ -20,6 +21,20 @@ function chartRange(options: HeadlessPaneOptionValues): TimeRange | null {
|
|
|
20
21
|
: null;
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
/**
|
|
25
|
+
* YTD as a window: from the prior year's last session to today, so a return
|
|
26
|
+
* comparison starts at the close year-to-date returns are measured from (31
|
|
27
|
+
* Dec 2025 for 2026), never at the year's first session. The earliest of the
|
|
28
|
+
* charted listings' last December sessions, a venue's own calendar where it
|
|
29
|
+
* is known.
|
|
30
|
+
*/
|
|
31
|
+
export function yearToDateWindow(spec: ChartSpec, now = Date.now()): { start: string; end: string } {
|
|
32
|
+
const today = new Date(now).toISOString().slice(0, 10);
|
|
33
|
+
const venues = [...new Set(spec.series.flatMap((entry) => entry.source.kind === "security" ? [entry.source.instrument.exchange || undefined] : []))];
|
|
34
|
+
const starts = (venues.length ? venues : [undefined]).flatMap((venue) => yearToDateBaseDay(today, venue, now) ?? []);
|
|
35
|
+
return { start: starts.sort()[0] ?? `${Number(today.slice(0, 4)) - 1}-12-31`, end: today };
|
|
36
|
+
}
|
|
37
|
+
|
|
23
38
|
function chartResolution(options: HeadlessPaneOptionValues): ChartResolution | null {
|
|
24
39
|
const value = options.chartResolution ?? options.resolution;
|
|
25
40
|
return typeof value === "string" && (CHART_RESOLUTIONS as readonly string[]).includes(value)
|
|
@@ -45,6 +60,10 @@ export function applyChartComposerCapabilityOptions(
|
|
|
45
60
|
options: HeadlessPaneOptionValues,
|
|
46
61
|
): ChartSpec {
|
|
47
62
|
let next = spec;
|
|
63
|
+
if ((options.rangePreset ?? options.range) === "YTD") {
|
|
64
|
+
// A year of daily bars, shown from the prior year's last close.
|
|
65
|
+
next = { ...next, viewport: { ...next.viewport, range: "1Y", dateWindow: yearToDateWindow(next) } };
|
|
66
|
+
}
|
|
48
67
|
const selectedRange = chartRange(options);
|
|
49
68
|
const range = capabilityId === "intraday-price-chart" && typeof options.session === "string"
|
|
50
69
|
? "1D"
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
} from "./catalog-inventory";
|
|
43
43
|
import { useCatalogUniverse, useRegisteredSeries } from "./use-series-catalog";
|
|
44
44
|
import { buildRegisteredSeriesChart } from "./presets";
|
|
45
|
+
import { currencyCodeLabel } from "../../../utils/currency-units";
|
|
45
46
|
|
|
46
47
|
type CatalogColumnId = "series" | "group" | "unit" | "frequency" | "publisher" | "expression";
|
|
47
48
|
type CatalogColumn = DataTableColumn & { id: CatalogColumnId };
|
|
@@ -228,7 +229,7 @@ export function DataCatalogPane({ focused, width, height }: PaneProps) {
|
|
|
228
229
|
case "group":
|
|
229
230
|
return { text: row.group, color: colors.textDim };
|
|
230
231
|
case "unit":
|
|
231
|
-
return { text: row.unit, color: colors.textDim };
|
|
232
|
+
return { text: currencyCodeLabel(row.unit), color: colors.textDim };
|
|
232
233
|
case "frequency":
|
|
233
234
|
return { text: FREQUENCY_LABELS[row.frequency], color: colors.textDim };
|
|
234
235
|
case "publisher":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { HeadlessPaneDefinition, HeadlessPaneOptionDef } from "../../../types/headless";
|
|
2
2
|
import { isSubMinuteInterval, subMinuteDataMessage } from "../../../cli/known-gaps";
|
|
3
|
-
import { FINANCIAL_PERIOD_OPTION, FINANCIAL_PERIOD_COUNT_OPTION, HISTORY_RANGE_VALUES } from "../shared/headless-options";
|
|
3
|
+
import { FINANCIAL_PERIOD_OPTION, FINANCIAL_PERIOD_COUNT_OPTION, HISTORY_RANGE_VALUES, RETURN_RANGE_VALUES } from "../shared/headless-options";
|
|
4
4
|
|
|
5
5
|
type PaneSchema = Pick<HeadlessPaneDefinition, "argument" | "options" | "discovery">;
|
|
6
6
|
|
|
@@ -111,10 +111,10 @@ export const paneSchemas = {
|
|
|
111
111
|
options: [
|
|
112
112
|
{
|
|
113
113
|
key: "rangePreset",
|
|
114
|
-
description: "Price-history window.",
|
|
114
|
+
description: "Price-history window. YTD runs from the prior year's last close.",
|
|
115
115
|
type: "enum",
|
|
116
116
|
aliases: ["range"],
|
|
117
|
-
values:
|
|
117
|
+
values: RETURN_RANGE_VALUES,
|
|
118
118
|
defaultValue: "1Y",
|
|
119
119
|
},
|
|
120
120
|
{
|
|
@@ -232,7 +232,8 @@ export async function loadChartPaneModel(
|
|
|
232
232
|
...(chart.priceHistoryIntegrity?.length ? { priceHistoryIntegrity: chart.priceHistoryIntegrity } : {}),
|
|
233
233
|
...(valuationPriceIssues.length ? { valuationPriceIssues } : {}),
|
|
234
234
|
...(periodCoverage.length ? { periodCoverage } : {}),
|
|
235
|
-
notices: [...chart.warnings.filter((warning) => warning === FINANCIAL_VINTAGE_NOTICE || warning === SEC_EPS_BASIS_NOTICE || warning === chart.priceComparison?.notice
|
|
235
|
+
notices: [...chart.warnings.filter((warning) => warning === FINANCIAL_VINTAGE_NOTICE || warning === SEC_EPS_BASIS_NOTICE || warning === chart.priceComparison?.notice
|
|
236
|
+
|| chart.delistings?.some((delisting) => delisting.notice === warning)), ...integrityNotices],
|
|
236
237
|
priceComparison: chart.priceComparison ?? null,
|
|
237
238
|
summaries: chart.series.filter((series) => !series.profile).map((series) => ({ id: series.id, ...summarizeResolvedSeries(series) })),
|
|
238
239
|
},
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useChartQueries, useResolvedEntryValue } from "../../../market-data/hooks";
|
|
3
|
+
import { instrumentFromTicker, type ChartRequest } from "../../../market-data/request-types";
|
|
4
|
+
import { buildChartKey } from "../../../market-data/selectors";
|
|
5
|
+
import { getTimeRangeForDateWindow } from "../../../time-series/date-window";
|
|
6
|
+
import type { TimeRange } from "../../../time-series/range";
|
|
7
|
+
import type { PricePoint } from "../../../types/financials";
|
|
8
|
+
import type { TickerRecord } from "../../../types/ticker";
|
|
9
|
+
import { resolveOptionsTarget } from "../../../utils/options";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The shortest range that reaches back to a contract's first daily bar. An
|
|
13
|
+
* option lists months before it expires and may trade a handful of times, so
|
|
14
|
+
* a stock's five years would hold one bar.
|
|
15
|
+
*/
|
|
16
|
+
function optionChartRange(points: readonly Pick<PricePoint, "date">[], now: number): TimeRange | null {
|
|
17
|
+
const first = points.reduce<number | null>((oldest, point) => {
|
|
18
|
+
const time = new Date(point.date).getTime();
|
|
19
|
+
return Number.isFinite(time) && (oldest === null || time < oldest) ? time : oldest;
|
|
20
|
+
}, null);
|
|
21
|
+
if (first === null) return null;
|
|
22
|
+
const range = getTimeRangeForDateWindow({ start: new Date(first), end: new Date(now) });
|
|
23
|
+
// A contract that first traded today still opens on a week: one session of minute bars reads as noise.
|
|
24
|
+
return range === "1D" ? "1W" : range;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The range an option contract's chart opens on, from its own daily history:
|
|
29
|
+
* a month until that answers, which is most of a weekly's life. Null for
|
|
30
|
+
* anything that is not an option.
|
|
31
|
+
*/
|
|
32
|
+
export function useOptionChartRange(ticker: TickerRecord | null | undefined): TimeRange | null {
|
|
33
|
+
const instrument = resolveOptionsTarget(ticker)?.isOptionTicker ? instrumentFromTicker(ticker) : null;
|
|
34
|
+
const request: ChartRequest | null = instrument
|
|
35
|
+
? { instrument, bufferRange: "ALL", granularity: "resolution", resolution: "1d" } : null;
|
|
36
|
+
const entries = useChartQueries(request ? [request] : []);
|
|
37
|
+
const history = useResolvedEntryValue(request ? entries.get(buildChartKey(request)) ?? null : null);
|
|
38
|
+
return useMemo(() => !request ? null : (history?.length ? optionChartRange(history, Date.now()) : null) ?? "1M", [history, !request]);
|
|
39
|
+
}
|
|
@@ -116,6 +116,7 @@ import { resolveChartComposerShortcut } from "./shortcuts";
|
|
|
116
116
|
import { describeChartResolution, formatChartDateWindow, formatChartResolution } from "./viewport-labels";
|
|
117
117
|
import { intradaySessionTimeZone } from "./session-history";
|
|
118
118
|
import { ChartSeriesQuickAdd } from "./quick-add";
|
|
119
|
+
import { ResearchTradeActions, researchTradeMatches, useResearchTrading } from "../shared/research-trading";
|
|
119
120
|
import { useLiveStreamingSetting } from "../../../state/hooks/live-streaming";
|
|
120
121
|
import { usePluginAppActions, usePluginConfigState } from "../../runtime";
|
|
121
122
|
import { usePluginRenderContext } from "../../runtime/context";
|
|
@@ -123,6 +124,7 @@ import { activePriceAlertsFor, addLevelAlert, PRICE_ALERTS_STORE, type AlertTarg
|
|
|
123
124
|
import { formatAlertDescription } from "../alerts/alert-engine";
|
|
124
125
|
import { alertInstrument, syncAlertQuoteStream } from "../alerts/live";
|
|
125
126
|
import { getSharedMarketDataCoordinator } from "../../../market-data/coordinator";
|
|
127
|
+
import { useOptionChartRange } from "./option-range";
|
|
126
128
|
import { getActiveQuoteDisplay } from "../../../market-data/market/status";
|
|
127
129
|
import { resolveEntryData } from "../../../market-data/selectors";
|
|
128
130
|
import { editPriceLevels, parsePriceLevels, PRICE_LEVELS_KEY, priceLevelTickerKey } from "./price-levels";
|
|
@@ -288,6 +290,7 @@ function ChartComposerSurface({
|
|
|
288
290
|
onCapture,
|
|
289
291
|
nameSeries = true,
|
|
290
292
|
}: ChartComposerSurfaceProps) {
|
|
293
|
+
const trade = useResearchTrading();
|
|
291
294
|
const dialog = useDialog();
|
|
292
295
|
const dispatch = useAppDispatch();
|
|
293
296
|
const { cellWidthPx = 8 } = useUiCapabilities();
|
|
@@ -514,6 +517,7 @@ function ChartComposerSurface({
|
|
|
514
517
|
return {
|
|
515
518
|
seriesId: listing.seriesId,
|
|
516
519
|
items: [
|
|
520
|
+
...(trade && researchTradeMatches(trade, { ...listing.instrument, exchange: listing.target.exchange }) ? trade.levels : []),
|
|
517
521
|
...stored.map((level) => ({
|
|
518
522
|
id: level.id,
|
|
519
523
|
value: level.price,
|
|
@@ -532,7 +536,7 @@ function ChartComposerSurface({
|
|
|
532
536
|
onEdit: editLevels,
|
|
533
537
|
...(alertsOn ? { action: { label: "alert", title: "Alert at Level", run: (level) => { void alertAtLevel(level.value); } } } : {}),
|
|
534
538
|
};
|
|
535
|
-
}, [alertAtLevel, alertsJson, alertsOn, editLevels, levelStore, listing]);
|
|
539
|
+
}, [alertAtLevel, alertsJson, alertsOn, editLevels, levelStore, listing, trade?.levels, trade?.contract]);
|
|
536
540
|
const [interactionCaptured, setInteractionCapturedState] = useState(false);
|
|
537
541
|
// Typing in quick-add must not freeze the plot: it only takes the keyboard.
|
|
538
542
|
const [modalCaptured, setModalCaptured] = useState(false);
|
|
@@ -949,8 +953,15 @@ function ChartComposerSurface({
|
|
|
949
953
|
? resolution.legendSeries?.filter((entry) => resolution.priceComparison?.seriesIds.includes(entry.id))
|
|
950
954
|
.map((entry) => `${entry.label}: no observations in the selected date range.`)
|
|
951
955
|
: []);
|
|
956
|
+
// A charted listing that no longer trades is said in the footer itself: "Delisted Oct 27, 2022" when it
|
|
957
|
+
// is the chart's one listing, which the title names, else "TWTR: Delisted Oct 27, 2022" for each.
|
|
958
|
+
const delistings = resolution.delistings;
|
|
959
|
+
const chartedListings = new Set(spec.series.flatMap((entry) => entry.source.kind === "security" ? [entry.source.instrument.symbol] : []));
|
|
960
|
+
const delistingKey = delistings?.length === 1 && chartedListings.size === 1 ? delistings[0]!.label
|
|
961
|
+
: delistings?.map((delisting) => delisting.notice).join(" · ") ?? "";
|
|
952
962
|
const statusWarnings = resolution.warnings.filter((warning) => (
|
|
953
963
|
warning !== FINANCIAL_VINTAGE_NOTICE && warning !== SEC_EPS_BASIS_NOTICE && warning !== comparisonNotice
|
|
964
|
+
&& !delistings?.some((delisting) => delisting.notice === warning)
|
|
954
965
|
&& !FORWARD_VALUATION_BASIS_NOTICES.has(warning)
|
|
955
966
|
&& !duplicateErrorNotices.has(warning) && !comparisonEmptyNotices.has(warning)
|
|
956
967
|
&& !spec.studies.some((study) => (
|
|
@@ -1002,21 +1013,26 @@ function ChartComposerSurface({
|
|
|
1002
1013
|
return newest > end ? formatCompositeDatasetDate(new Date(newest), end) : null;
|
|
1003
1014
|
}, [activeRuntimeViewport, plottedSeries, spec, viewport]);
|
|
1004
1015
|
usePaneFooter(footerId, () => ({
|
|
1005
|
-
info:
|
|
1006
|
-
? [{ id: "
|
|
1007
|
-
|
|
1008
|
-
? [{ id: "
|
|
1009
|
-
:
|
|
1010
|
-
? [{ id: "
|
|
1011
|
-
:
|
|
1016
|
+
info: [
|
|
1017
|
+
...(delistingKey ? [{ id: "delisted", parts: [{ text: delistingKey, tone: "warning" as const }] }] : []),
|
|
1018
|
+
...(loading
|
|
1019
|
+
? [{ id: "loading", parts: [{ text: "loading", tone: "muted" as const }] }]
|
|
1020
|
+
: pickingAnchor
|
|
1021
|
+
? [{ id: "anchor", parts: [{ text: "pick a bar to anchor VWAP", tone: "muted" as const }] }]
|
|
1022
|
+
: newestBeyondView
|
|
1023
|
+
? [{ id: "newest", asOf: true, parts: [{ text: `newest ${newestBeyondView}`, tone: "muted" as const }] }]
|
|
1024
|
+
: []),
|
|
1025
|
+
],
|
|
1012
1026
|
hints: [
|
|
1027
|
+
// One key opens the ticket, which chooses Buy or Sell; [s]eries keeps its key with trading on.
|
|
1028
|
+
...(trade?.available ? [{ id: "trade", key: "t", label: "rade", title: "Open the trade panel", onPress: () => trade.open(), disabled: trade.busy }] : []),
|
|
1013
1029
|
{ id: "series", key: "s", label: "eries", onPress: footerSeries },
|
|
1014
1030
|
{ id: "indicators", key: "i", label: "ndicators", onPress: openIndicators, disabled: indicatorsDisabled },
|
|
1015
1031
|
{ id: "formulas", key: "f", label: "ormulas", onPress: openFormulas, disabled: formulasDisabled },
|
|
1016
|
-
{ id: "resolution", key: "
|
|
1032
|
+
{ id: "resolution", key: "b", label: "ars", title: "Bar Size", onPress: footerResolution },
|
|
1017
1033
|
...(anchorable ? [{ id: "vwap-anchor", key: "v", label: "wap anchor", title: "Anchor VWAP", onPress: footerAnchor }] : []),
|
|
1018
1034
|
],
|
|
1019
|
-
}), [loading, pickingAnchor, newestBeyondView, footerSeries, openIndicators, indicatorsDisabled, openFormulas, formulasDisabled, footerResolution, anchorable, footerAnchor]);
|
|
1035
|
+
}), [delistingKey, loading, pickingAnchor, newestBeyondView, footerSeries, openIndicators, indicatorsDisabled, openFormulas, formulasDisabled, footerResolution, anchorable, footerAnchor, trade]);
|
|
1020
1036
|
|
|
1021
1037
|
// A fixed window (a GIP session) highlights no range, so the bar names it.
|
|
1022
1038
|
const dateWindowLabel = useMemo(() => {
|
|
@@ -1097,6 +1113,8 @@ function ChartComposerSurface({
|
|
|
1097
1113
|
|
|
1098
1114
|
return (
|
|
1099
1115
|
<Box flexDirection="column" width={width} height={height}>
|
|
1116
|
+
{/* Renders nothing: b and s open the trade panel. The query bar keeps the full row. */}
|
|
1117
|
+
<ResearchTradeActions focused={shortcutActive} />
|
|
1100
1118
|
<QueryBar
|
|
1101
1119
|
width={width}
|
|
1102
1120
|
filters={[
|
|
@@ -1286,7 +1304,9 @@ export function ChartComposerPane({ paneId, focused, width, height }: PaneProps)
|
|
|
1286
1304
|
export function ChartComposerResearchTab({ focused, width, height, onCapture }: TickerResearchTabProps) {
|
|
1287
1305
|
const { symbol: paneSymbol, ticker } = usePaneTicker();
|
|
1288
1306
|
const symbol = paneSymbol ? publicTickerKey(paneSymbol, ticker?.metadata.exchange) : null;
|
|
1289
|
-
|
|
1307
|
+
// An option contract opens on the range its own history fills; a chart the researcher set keeps its range.
|
|
1308
|
+
const optionRange = useOptionChartRange(ticker);
|
|
1309
|
+
const fallback = useMemo(() => symbol ? buildPriceChartPreset(symbol, optionRange ?? undefined) : buildEmptyChartPreset(), [optionRange, symbol]);
|
|
1290
1310
|
const [storedSpec, setStoredSpec] = usePaneSettingValue<unknown>(CHART_SPEC_SETTING_KEY, fallback);
|
|
1291
1311
|
const spec = useMemo(() => parseChartSpecOr(storedSpec, fallback), [fallback, storedSpec]);
|
|
1292
1312
|
const previousSymbolRef = useRef(symbol);
|
|
@@ -222,11 +222,11 @@ function placedRegisteredSeries(expression: ParsedSeriesExpression) {
|
|
|
222
222
|
};
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
export function buildPriceChartPreset(symbol: string): ChartSpec {
|
|
225
|
+
export function buildPriceChartPreset(symbol: string, range?: TimeRange): ChartSpec {
|
|
226
226
|
const normalized = normalizeInstrument(symbol, true);
|
|
227
227
|
if (!normalized) return buildEmptyChartPreset();
|
|
228
228
|
return setBuiltinStudies(
|
|
229
|
-
chartSpec([buildSeriesSpec({ kind: "security", ...normalized, fieldId: CHART_FIELD_IDS.price }, 0)]),
|
|
229
|
+
chartSpec([buildSeriesSpec({ kind: "security", ...normalized, fieldId: CHART_FIELD_IDS.price }, 0)], { range }),
|
|
230
230
|
["volume"],
|
|
231
231
|
);
|
|
232
232
|
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from "../../../utils/exchanges";
|
|
16
16
|
import { parseSeriesExpression, type ParsedSeriesExpression } from "./series-expression";
|
|
17
17
|
import { parseFormulaExpression } from "./formula";
|
|
18
|
+
import { currencyCodeLabel } from "../../../utils/currency-units";
|
|
18
19
|
|
|
19
20
|
export interface SeriesCatalogInstrument {
|
|
20
21
|
symbol: string;
|
|
@@ -351,11 +352,12 @@ function registeredSeriesSuggestions(
|
|
|
351
352
|
}
|
|
352
353
|
hits.sort((left, right) => left.rank - right.rank || left.entry.name.length - right.entry.name.length);
|
|
353
354
|
return hits.map(({ entry, expression, lines, formula, symbol }): SeriesCatalogSuggestion => {
|
|
354
|
-
const
|
|
355
|
+
const unit = currencyCodeLabel(entry.unit);
|
|
356
|
+
const description = [entry.group, unit, entry.frequency].filter(Boolean).join(" · ");
|
|
355
357
|
const detail = entry.group ?? "Series";
|
|
356
358
|
if (formula !== undefined) {
|
|
357
359
|
// A formula runs long: its name leads, and the formula is in the description.
|
|
358
|
-
const formulaDescription = [formula,
|
|
360
|
+
const formulaDescription = [formula, unit, entry.frequency].filter(Boolean).join(" · ");
|
|
359
361
|
return { id: `formula:${formula}`, label: entry.name, description: formulaDescription, detail, formula };
|
|
360
362
|
}
|
|
361
363
|
const placed = {
|
|
@@ -13,11 +13,11 @@ export function resolveChartComposerShortcut(
|
|
|
13
13
|
): ChartComposerShortcut | null {
|
|
14
14
|
if (event.defaultPrevented || event.propagationStopped || event.targetEditable) return null;
|
|
15
15
|
|
|
16
|
-
// `r` is the app-wide refresh key, so it reloads here too and
|
|
17
|
-
//
|
|
16
|
+
// `r` is the app-wide refresh key, so it reloads here too, and `t` is Trade
|
|
17
|
+
// wherever a chart can trade: the resolution picker is [b]ars, the bar size.
|
|
18
18
|
if (isPlainKey(event, "s")) return "series";
|
|
19
19
|
if (isPlainKey(event, "r")) return "reload";
|
|
20
|
-
if (isPlainKey(event, "
|
|
20
|
+
if (isPlainKey(event, "b")) return "resolution";
|
|
21
21
|
if (!isPlainKey(event, event.name ?? "")) return null;
|
|
22
22
|
|
|
23
23
|
const rangeIndex = Number(event.name) - 1;
|
|
@@ -55,7 +55,8 @@ function calendarParts(date: Date, timeZone: string): { key: string; year: numbe
|
|
|
55
55
|
/**
|
|
56
56
|
* A fixed chart window as the bar states it: one exchange session reads as
|
|
57
57
|
* "Sep 23 session", a longer window as "Sep 17 to Sep 23". Dates are taken in
|
|
58
|
-
* the exchange's zone, so a session that crosses UTC midnight stays one day
|
|
58
|
+
* the exchange's zone, so a session that crosses UTC midnight stays one day;
|
|
59
|
+
* a window of calendar days (YYYY-MM-DD) names those days.
|
|
59
60
|
*/
|
|
60
61
|
export function formatChartDateWindow(
|
|
61
62
|
window: { start: string; end: string },
|
|
@@ -65,7 +66,9 @@ export function formatChartDateWindow(
|
|
|
65
66
|
const start = new Date(window.start);
|
|
66
67
|
const end = new Date(window.end);
|
|
67
68
|
if (!Number.isFinite(start.getTime()) || !Number.isFinite(end.getTime())) return undefined;
|
|
68
|
-
|
|
69
|
+
// A window of calendar days (2025-12-31 to 2026-10-20) names those days, not UTC midnight in the exchange's zone.
|
|
70
|
+
const calendarDays = [window.start, window.end].every((value) => /^\d{4}-\d{2}-\d{2}$/.test(value));
|
|
71
|
+
let zone = calendarDays ? "UTC" : timeZone;
|
|
69
72
|
try {
|
|
70
73
|
new Intl.DateTimeFormat("en-US", { timeZone: zone });
|
|
71
74
|
} catch {
|
|
@@ -136,7 +136,7 @@ export const chatModule: PluginModule = {
|
|
|
136
136
|
id: UNREAD_INBOX_TEMPLATE_ID,
|
|
137
137
|
paneId: UNREAD_INBOX_PANE_ID,
|
|
138
138
|
label: "Unread",
|
|
139
|
-
description: "Unread chat, then price alerts, catalysts and team notices you missed",
|
|
139
|
+
description: "Unread chat, then price alerts, catalysts, economic releases and team notices you missed",
|
|
140
140
|
keywords: ["unread", "chat", "inbox", "mentions", "missed", "notifications", "alerts"],
|
|
141
141
|
createInstance: () => ({
|
|
142
142
|
placement: "floating",
|
|
@@ -49,7 +49,7 @@ export function ChatStatusWidget({ controller = chatController }: ChatStatusWidg
|
|
|
49
49
|
const [username, setUsername] = useState<string | null>(initialSnapshot.user?.username ?? null);
|
|
50
50
|
const [hasSavedSession, setHasSavedSession] = useState(initialSnapshot.hasSavedSession);
|
|
51
51
|
const [snapshot, setSnapshot] = useState(initialSnapshot);
|
|
52
|
-
// What the Unread list shows as unread: chat, then missed price, catalyst and team notices.
|
|
52
|
+
// What the Unread list shows as unread: chat, then missed price, catalyst, economic release and team notices.
|
|
53
53
|
const notices = useSyncExternalStore(subscribeNotificationLog, getNotificationLog, getNotificationLog);
|
|
54
54
|
const unreadCount = unreadTotal(snapshot, notices);
|
|
55
55
|
|
|
@@ -55,6 +55,7 @@ const COLUMNS: DataTableColumn[] = [
|
|
|
55
55
|
const NOTICE_LABELS: Record<MissedNoticeKind, string> = {
|
|
56
56
|
price: "Price alert",
|
|
57
57
|
catalyst: "Catalyst",
|
|
58
|
+
econ: "Economic release",
|
|
58
59
|
team: "Team",
|
|
59
60
|
};
|
|
60
61
|
|
|
@@ -193,7 +194,7 @@ export function UnreadInboxPane({
|
|
|
193
194
|
});
|
|
194
195
|
}, [createPaneFromTemplate, dispatch, inboxInstanceId, stateRef]);
|
|
195
196
|
|
|
196
|
-
// A notice opens where its toast did: the price rule on ALRT Prices, the catalyst, the team card's place.
|
|
197
|
+
// A notice opens where its toast did: the price rule on ALRT Prices, the catalyst, ECO, the team card's place.
|
|
197
198
|
const openNotice = useCallback((notice: NotificationLogEntry) => {
|
|
198
199
|
const ref = parseNoticeRef(notice.refId);
|
|
199
200
|
if (!ref) return;
|
|
@@ -207,6 +208,10 @@ export function UnreadInboxPane({
|
|
|
207
208
|
createPaneFromTemplate("catalysts-pane", { values: { event: ref.id } });
|
|
208
209
|
return;
|
|
209
210
|
}
|
|
211
|
+
if (ref.kind === "econ") {
|
|
212
|
+
showPane("econ-calendar");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
210
215
|
const card = teamStore.getSnapshot().notifications.find((entry) => entry.id === ref.id) ?? null;
|
|
211
216
|
const action = card ? teamStore.notificationAction(card) : null;
|
|
212
217
|
if (action) action.onClick();
|
|
@@ -93,7 +93,7 @@ export function listUnreadInboxItems(options: {
|
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
95
|
* A row of the Unread list: a channel with unread chat, or a missed price,
|
|
96
|
-
* catalyst or team notice from the device's log, read or not.
|
|
96
|
+
* catalyst, economic release or team notice from the device's log, read or not.
|
|
97
97
|
*/
|
|
98
98
|
export type UnreadListRow =
|
|
99
99
|
| { kind: "chat"; key: string; item: UnreadInboxItem }
|
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
TeamSentInvitation,
|
|
6
6
|
TeamSummary,
|
|
7
7
|
} from "../../../../api-client";
|
|
8
|
-
import { Button } from "../../../../components";
|
|
8
|
+
import { Button, Notice } from "../../../../components";
|
|
9
9
|
import { colors } from "../../../../theme/colors";
|
|
10
10
|
import { Box, Text, TextAttributes } from "../../../../ui";
|
|
11
11
|
import { WallTeaser } from "../../shared/wall-teaser";
|
|
@@ -122,6 +122,8 @@ export function InvitesSection({
|
|
|
122
122
|
team,
|
|
123
123
|
invitations,
|
|
124
124
|
links,
|
|
125
|
+
invitationsError,
|
|
126
|
+
linksError,
|
|
125
127
|
username,
|
|
126
128
|
width,
|
|
127
129
|
busy,
|
|
@@ -135,6 +137,9 @@ export function InvitesSection({
|
|
|
135
137
|
team: TeamSummary;
|
|
136
138
|
invitations: readonly TeamSentInvitation[];
|
|
137
139
|
links: readonly TeamInviteLink[];
|
|
140
|
+
/** Set when that list did not load: it shows the error, not an empty list. */
|
|
141
|
+
invitationsError: string | null;
|
|
142
|
+
linksError: string | null;
|
|
138
143
|
username: string;
|
|
139
144
|
width: number;
|
|
140
145
|
busy: string | null;
|
|
@@ -170,7 +175,8 @@ export function InvitesSection({
|
|
|
170
175
|
|
|
171
176
|
{manage ? (
|
|
172
177
|
<Box flexDirection="column" gap={1}>
|
|
173
|
-
<SectionTitle detail={invitations.length === 0 ? "none pending" : undefined}>Pending</SectionTitle>
|
|
178
|
+
<SectionTitle detail={invitations.length === 0 && !invitationsError ? "none pending" : undefined}>Pending</SectionTitle>
|
|
179
|
+
{invitationsError ? <Notice tone="negative">{invitationsError}</Notice> : null}
|
|
174
180
|
{invitations.map((invitation) => (
|
|
175
181
|
<AccentRow key={invitation.id} accent={colors.textMuted} width={width}>
|
|
176
182
|
<Text width={18} fg={colors.text}>
|
|
@@ -191,9 +197,10 @@ export function InvitesSection({
|
|
|
191
197
|
) : null}
|
|
192
198
|
|
|
193
199
|
<Box flexDirection="column" gap={1}>
|
|
194
|
-
<SectionTitle detail={canLink ? `${links.length} of 10 active` : undefined}>Invite links</SectionTitle>
|
|
200
|
+
<SectionTitle detail={canLink && !linksError ? `${links.length} of 10 active` : undefined}>Invite links</SectionTitle>
|
|
195
201
|
{canLink ? (
|
|
196
202
|
<>
|
|
203
|
+
{linksError ? <Notice tone="negative">{linksError}</Notice> : null}
|
|
197
204
|
{links.map((link) => {
|
|
198
205
|
const uses = link.maxUses === null ? `${link.uses} uses` : `${link.uses}/${link.maxUses} uses`;
|
|
199
206
|
return (
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
type TeamSummary,
|
|
10
10
|
} from "../../../../api-client";
|
|
11
11
|
import { ApiRequestError } from "../../../../api-client/errors";
|
|
12
|
-
import { QueryBar, loadingText, useFieldRing, usePaneFooter, usePaneMenuItems, usePaneTabs, type PaneFooterSegment, type PaneHint } from "../../../../components";
|
|
12
|
+
import { QueryBar, loadingText, unavailableText, useFieldRing, usePaneFooter, usePaneMenuItems, usePaneTabs, type PaneFooterSegment, type PaneHint } from "../../../../components";
|
|
13
13
|
import { useShortcut } from "../../../../react/input";
|
|
14
14
|
import { colors } from "../../../../theme/colors";
|
|
15
15
|
import type { PaneProps } from "../../../../types/plugin";
|
|
@@ -91,11 +91,25 @@ interface TeamDetails {
|
|
|
91
91
|
members: TeamMember[];
|
|
92
92
|
invitations: TeamSentInvitation[];
|
|
93
93
|
links: TeamInviteLink[];
|
|
94
|
+
/** Why the sent invitations or the invite links did not load; the section says so instead of listing none. */
|
|
95
|
+
invitationsError: string | null;
|
|
96
|
+
linksError: string | null;
|
|
94
97
|
loading: boolean;
|
|
95
98
|
error: string | null;
|
|
96
99
|
}
|
|
97
100
|
|
|
98
|
-
const EMPTY_DETAILS: TeamDetails = {
|
|
101
|
+
const EMPTY_DETAILS: TeamDetails = {
|
|
102
|
+
members: [], invitations: [], links: [], invitationsError: null, linksError: null, loading: false, error: null,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/** A list the team pane loads beside the members: its rows, or why they did not load. */
|
|
106
|
+
async function settleList<T>(request: Promise<T[]>, fallback: string): Promise<{ rows: T[]; error: string | null }> {
|
|
107
|
+
try {
|
|
108
|
+
return { rows: await request, error: null };
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return { rows: [], error: error instanceof Error && error.message ? error.message : fallback };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
99
113
|
|
|
100
114
|
/**
|
|
101
115
|
* Members, sent invitations, and invite links for the team on screen. Reloads
|
|
@@ -119,17 +133,22 @@ function useTeamDetails(team: TeamSummary | null) {
|
|
|
119
133
|
const manage = canManageTeam(role);
|
|
120
134
|
const canLink = manage || allowMemberInvites;
|
|
121
135
|
try {
|
|
136
|
+
const none = { rows: [], error: null };
|
|
122
137
|
const [membersResult, invitations, links] = await Promise.all([
|
|
123
138
|
apiClient.getTeamMembers(teamId),
|
|
124
|
-
manage
|
|
125
|
-
|
|
126
|
-
: Promise.resolve([]),
|
|
127
|
-
canLink
|
|
128
|
-
? apiClient.listTeamInviteLinks(teamId).catch(logFailure("cloud.team.pane", "Load team invite links failed", []))
|
|
129
|
-
: Promise.resolve([]),
|
|
139
|
+
manage ? settleList(apiClient.listTeamInvitations(teamId), unavailableText("Pending invitations")) : none,
|
|
140
|
+
canLink ? settleList(apiClient.listTeamInviteLinks(teamId), unavailableText("Invite links")) : none,
|
|
130
141
|
]);
|
|
131
142
|
if (generation.current !== current) return;
|
|
132
|
-
setDetails({
|
|
143
|
+
setDetails({
|
|
144
|
+
members: membersResult.members,
|
|
145
|
+
invitations: invitations.rows,
|
|
146
|
+
links: links.rows,
|
|
147
|
+
invitationsError: invitations.error,
|
|
148
|
+
linksError: links.error,
|
|
149
|
+
loading: false,
|
|
150
|
+
error: null,
|
|
151
|
+
});
|
|
133
152
|
} catch (error) {
|
|
134
153
|
if (generation.current !== current) return;
|
|
135
154
|
setDetails((previous) => ({ ...previous, loading: false, error: errorText(error, "Could not load the team.") }));
|
|
@@ -689,6 +708,8 @@ export function TeamPane({ focused, width, height, close }: PaneProps) {
|
|
|
689
708
|
team={team}
|
|
690
709
|
invitations={details.invitations}
|
|
691
710
|
links={details.links}
|
|
711
|
+
invitationsError={details.invitationsError}
|
|
712
|
+
linksError={details.linksError}
|
|
692
713
|
username={inviteUsername}
|
|
693
714
|
width={contentWidth}
|
|
694
715
|
busy={busy}
|
|
@@ -21,8 +21,10 @@ export interface CommodityBalances {
|
|
|
21
21
|
const finite = (value: unknown): value is number => typeof value === "number" && Number.isFinite(value);
|
|
22
22
|
const text = (value: unknown): value is string => typeof value === "string";
|
|
23
23
|
|
|
24
|
+
const MONTH = /^\d{4}-(0[1-9]|1[0-2])$/;
|
|
24
25
|
const validRow = (row: BalanceRow) => [row.id, row.crop, row.country, row.attribute, row.unit, row.year].every(text)
|
|
25
|
-
&& [row.value, row.cropOrder, row.countryOrder, row.attributeOrder].every(finite)
|
|
26
|
+
&& [row.value, row.cropOrder, row.countryOrder, row.attributeOrder].every(finite)
|
|
27
|
+
&& (row.month == null || (text(row.month) && MONTH.test(row.month)));
|
|
26
28
|
const validPrice = (row: PriceForecast) => [row.id, row.crop, row.unit, row.season].every(text)
|
|
27
29
|
&& [row.forecast, row.cropOrder, row.seasonYear].every(finite);
|
|
28
30
|
|
|
@@ -31,7 +33,7 @@ function readBoard(payload: PsdBoardPayload): CommodityBalances {
|
|
|
31
33
|
if (!payload || payload.source !== "USDA" || !Array.isArray(payload.rows) || !Array.isArray(payload.prices)
|
|
32
34
|
|| !Array.isArray(payload.gaps) || !payload.gaps.every(text)
|
|
33
35
|
|| !payload.rows.every(validRow) || !payload.prices.every(validPrice)
|
|
34
|
-
|| (payload.month != null &&
|
|
36
|
+
|| (payload.month != null && !MONTH.test(payload.month))
|
|
35
37
|
|| (payload.marketYear != null && !text(payload.marketYear))) {
|
|
36
38
|
throw new Error("The server returned an invalid crop balance board");
|
|
37
39
|
}
|
|
@@ -47,8 +49,8 @@ function readBoard(payload: PsdBoardPayload): CommodityBalances {
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
/**
|
|
50
|
-
* USDA's world wheat, corn, soybean and
|
|
51
|
-
* year, and the season-average price forecasts. Gloom Cloud reads USDA's
|
|
52
|
+
* USDA's world wheat, corn, soybean, cotton, coffee and sugar balances for the
|
|
53
|
+
* latest market year, and the season-average price forecasts. Gloom Cloud reads USDA's
|
|
52
54
|
* files; a route the server does not serve yet (404) or cannot read (503) is
|
|
53
55
|
* the not-available message.
|
|
54
56
|
*/
|