gloomberb 0.18.1 → 0.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +106 -5
- package/src/api-client/commodity-balances.ts +6 -0
- package/src/api-client/company-kpis.ts +9 -0
- package/src/api-client/crypto-markets.ts +9 -0
- package/src/api-client/data.ts +21 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/index.ts +2 -0
- package/src/api-client/request.ts +5 -3
- package/src/api-client/telemetry.ts +17 -0
- package/src/api-client/types.ts +5 -0
- package/src/app/global-shortcuts.ts +1 -16
- package/src/app/pane-runtime/plugin-bindings.ts +3 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
- package/src/app/runtime/plugin-bindings.ts +6 -2
- package/src/app/runtime/update.ts +28 -8
- package/src/architecture/pane-tabs-allowlist.json +65 -0
- package/src/brokers/logos.ts +66 -0
- package/src/brokers/profile-form.ts +3 -0
- package/src/brokers/remote-broker-adapter.ts +13 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/service-capabilities.ts +7 -1
- package/src/cli/base-converter.ts +60 -18
- package/src/cli/commands/market.ts +354 -187
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +167 -60
- package/src/cli/currency-hints.ts +27 -0
- package/src/cli/fx-pair.ts +41 -4
- package/src/cli/glossary.ts +57 -4
- package/src/cli/help.ts +1 -1
- package/src/cli/helpers.ts +36 -3
- package/src/cli/index.ts +12 -1
- package/src/cli/issuer-membership.ts +103 -0
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +51 -3
- package/src/cli/name-entries.ts +51 -12
- package/src/cli/pane-functions/capabilities.ts +2 -0
- package/src/cli/pane-functions/catalog.ts +19 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +11 -5
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +27 -3
- package/src/cli/pane-functions/headless.ts +31 -11
- package/src/cli/pane-functions/index.ts +67 -7
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +21 -6
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/report-tables.ts +136 -18
- package/src/cli/result.ts +12 -4
- package/src/cli/series-search.ts +2 -1
- package/src/cli/symbol-list.ts +47 -11
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +25 -7
- package/src/components/chart/composite/drawing-store.ts +26 -6
- package/src/components/chart/composite/format.ts +13 -1
- package/src/components/chart/composite/interactions.ts +2 -1
- package/src/components/chart/composite/level-labels.ts +30 -0
- package/src/components/chart/composite/levels.ts +17 -6
- package/src/components/chart/composite/panel-surface.tsx +69 -8
- package/src/components/chart/composite/types.ts +6 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +1 -1
- package/src/components/command-bar/routes/root/command-items.ts +32 -6
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/data-table/view.tsx +23 -3
- package/src/components/index.ts +3 -0
- package/src/components/layout/detached-pane-shell.tsx +1 -0
- package/src/components/layout/floating-pane.tsx +1 -0
- package/src/components/layout/header.tsx +228 -55
- package/src/components/layout/pane/content.tsx +16 -14
- package/src/components/layout/pane/footer/index.tsx +22 -3
- package/src/components/layout/pane/footer/model.ts +99 -4
- package/src/components/layout/pane/header-tabs.tsx +15 -1
- package/src/components/layout/pane/header.tsx +10 -3
- package/src/components/layout/pane/index.tsx +1 -0
- package/src/components/layout/pane/pane-tabs.tsx +9 -1
- package/src/components/onboarding/onboarding-frame.tsx +9 -11
- package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
- package/src/components/onboarding/wizard-model.ts +10 -3
- package/src/components/ui/button.tsx +11 -4
- package/src/components/ui/choice-dialog.tsx +8 -5
- package/src/components/ui/display.tsx +11 -6
- package/src/components/ui/fields.tsx +100 -20
- package/src/components/ui/icon.tsx +6 -1
- package/src/components/ui/list-row-lines.tsx +87 -0
- package/src/components/ui/list-view.tsx +14 -0
- package/src/components/ui/logo-mark.tsx +64 -0
- package/src/components/ui/number-step.ts +54 -0
- package/src/components/ui/select-button.tsx +5 -4
- package/src/components/ui/select-field.tsx +3 -3
- package/src/components/ui/stat-grid.tsx +28 -3
- package/src/components/ui/status.tsx +20 -4
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- package/src/core/app-services.ts +4 -0
- package/src/core/state/app/instrument.ts +8 -3
- package/src/data/config/store/normalize.ts +4 -1
- package/src/data/plugin-cache.ts +32 -6
- package/src/docs/snapshot.json +5 -5
- package/src/i18n/es.ts +31 -3
- package/src/i18n/ja.ts +31 -3
- package/src/i18n/ko.ts +31 -3
- package/src/i18n/zh-cn.ts +32 -4
- package/src/i18n/zh-tw.ts +32 -4
- package/src/market-data/commodity-names.ts +80 -0
- package/src/market-data/coordinator/auxiliary.ts +3 -1
- package/src/market-data/coordinator/chart.ts +3 -1
- package/src/market-data/futures-board.ts +38 -1
- package/src/market-data/futures-contract.ts +91 -0
- package/src/market-data/fx-closes.ts +78 -1
- package/src/market-data/market/change-basis.ts +219 -0
- package/src/market-data/market/format.ts +28 -12
- package/src/market-data/market/freshness.ts +38 -0
- package/src/market-data/market/status.ts +55 -9
- package/src/market-data/market/year-to-date.ts +46 -0
- package/src/market-data/quotes/contributions.ts +12 -0
- package/src/market-data/quotes/delisting.ts +34 -0
- package/src/market-data/quotes/live-valuation.ts +23 -0
- package/src/market-data/quotes/resolution.ts +8 -2
- package/src/market-data/tax-equivalent.ts +10 -2
- package/src/notifications/app-notifier.ts +2 -2
- package/src/notifications/notification-log.ts +8 -6
- package/src/plugins/builtin/alerts/alert-engine.ts +3 -1
- package/src/plugins/builtin/alerts/cli.ts +26 -18
- package/src/plugins/builtin/alerts/events.ts +1 -0
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/alerts/research-rules.ts +1 -0
- package/src/plugins/builtin/analytics/risk-pane.tsx +10 -7
- package/src/plugins/builtin/attention/pane.tsx +16 -14
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/awards/pane.tsx +6 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- package/src/plugins/builtin/backtest/export.ts +1 -1
- package/src/plugins/builtin/backtest/window.ts +16 -3
- package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
- package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
- package/src/plugins/builtin/broker-manager/index.tsx +20 -9
- package/src/plugins/builtin/broker-manager/model.ts +5 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
- package/src/plugins/builtin/broker-manager/table.ts +5 -1
- package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
- package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
- package/src/plugins/builtin/broker-trading/command.ts +103 -0
- package/src/plugins/builtin/broker-trading/controller.ts +688 -0
- package/src/plugins/builtin/broker-trading/dock.tsx +184 -0
- package/src/plugins/builtin/broker-trading/headless.ts +76 -0
- package/src/plugins/builtin/broker-trading/index.tsx +77 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
- package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
- package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
- package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
- package/src/plugins/builtin/broker-trading/pane.tsx +507 -0
- package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
- package/src/plugins/builtin/broker-trading/ticket.tsx +578 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
- package/src/plugins/builtin/buildout/format.ts +1 -0
- package/src/plugins/builtin/buildout/model/index.ts +16 -7
- package/src/plugins/builtin/buildout/pane/index.tsx +11 -7
- package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
- package/src/plugins/builtin/catalysts/client.ts +2 -2
- package/src/plugins/builtin/catalysts/pane.tsx +3 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
- package/src/plugins/builtin/chart-composer/headless.ts +2 -1
- package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +32 -11
- package/src/plugins/builtin/chart-composer/presets.ts +2 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
- package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
- package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
- package/src/plugins/builtin/commodity-balances/client.ts +6 -4
- package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
- package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
- package/src/plugins/builtin/commodity-balances/model.ts +22 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +39 -9
- package/src/plugins/builtin/company-attention/app-rank.tsx +4 -5
- package/src/plugins/builtin/company-attention/pane.tsx +13 -11
- package/src/plugins/builtin/company-kpis/client.ts +3 -2
- package/src/plugins/builtin/company-kpis/headless.ts +5 -7
- package/src/plugins/builtin/company-kpis/model.ts +85 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +30 -15
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- package/src/plugins/builtin/correlation/headless.ts +2 -1
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -0
- package/src/plugins/builtin/correlation/settings.ts +1 -0
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cot/index.tsx +1 -1
- package/src/plugins/builtin/cot/model.ts +35 -12
- package/src/plugins/builtin/cot/pane.tsx +4 -3
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
- package/src/plugins/builtin/credit-documents/pane.tsx +8 -7
- package/src/plugins/builtin/crypto-board/client.ts +33 -2
- package/src/plugins/builtin/crypto-board/headless.ts +5 -2
- package/src/plugins/builtin/crypto-board/model.ts +28 -8
- package/src/plugins/builtin/debt-maturities/pane.tsx +4 -3
- package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/doe/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
- package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
- package/src/plugins/builtin/econ/calendar-model.ts +35 -0
- package/src/plugins/builtin/econ/index.tsx +31 -4
- package/src/plugins/builtin/econ/print-watch.ts +279 -0
- package/src/plugins/builtin/equity-screener/headless.ts +30 -5
- package/src/plugins/builtin/equity-screener/model.ts +68 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +129 -119
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
- package/src/plugins/builtin/exposure/client.ts +4 -1
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/exposure/evidence.ts +4 -1
- package/src/plugins/builtin/exposure/holdings.ts +3 -1
- package/src/plugins/builtin/exposure/pane.tsx +43 -21
- package/src/plugins/builtin/filing-events/index.tsx +9 -3
- package/src/plugins/builtin/futures/table.tsx +6 -3
- package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +4 -1
- package/src/plugins/builtin/gpu/changes.tsx +10 -10
- package/src/plugins/builtin/gpu/equities.tsx +4 -1
- package/src/plugins/builtin/gpu/history.tsx +4 -2
- package/src/plugins/builtin/gpu/pane.tsx +5 -3
- package/src/plugins/builtin/holders/client.ts +2 -1
- package/src/plugins/builtin/holders/headless.ts +2 -1
- package/src/plugins/builtin/holders/thirteenf-match.ts +3 -1
- package/src/plugins/builtin/insider/base-values.ts +76 -0
- package/src/plugins/builtin/insider/client.ts +3 -0
- package/src/plugins/builtin/insider/headless.ts +48 -10
- package/src/plugins/builtin/insider/index.tsx +17 -8
- package/src/plugins/builtin/insider/issuer.ts +34 -4
- package/src/plugins/builtin/insider/signals-client.ts +2 -2
- package/src/plugins/builtin/insider/table-model.ts +35 -13
- package/src/plugins/builtin/insider/table.tsx +5 -2
- package/src/plugins/builtin/iv-history/events.ts +145 -0
- package/src/plugins/builtin/iv-history/format.ts +3 -0
- package/src/plugins/builtin/iv-history/headless.ts +91 -7
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- package/src/plugins/builtin/jodi/pane.tsx +4 -4
- package/src/plugins/builtin/market-halts/model.ts +31 -6
- package/src/plugins/builtin/market-halts/pane.tsx +3 -0
- package/src/plugins/builtin/market-movers/client.ts +4 -3
- package/src/plugins/builtin/market-movers/screener.ts +4 -3
- package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/notes/cli.ts +1 -1
- package/src/plugins/builtin/notes/export.ts +17 -6
- package/src/plugins/builtin/notes/files.ts +16 -5
- package/src/plugins/builtin/options/enrichment-model.ts +10 -9
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/model-spot.ts +70 -4
- package/src/plugins/builtin/options/view.tsx +118 -35
- package/src/plugins/builtin/options-calculator/pane.tsx +7 -4
- package/src/plugins/builtin/options-positioning/headless.ts +11 -3
- package/src/plugins/builtin/options-positioning/model.ts +24 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +10 -12
- package/src/plugins/builtin/options-scenario/client.ts +19 -1
- package/src/plugins/builtin/options-scenario/headless.ts +13 -6
- package/src/plugins/builtin/options-scenario/pane.tsx +54 -11
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +5 -4
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -5
- package/src/plugins/builtin/portfolio-list/cli/render.ts +34 -10
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- package/src/plugins/builtin/power/client.ts +2 -2
- package/src/plugins/builtin/power/pane.tsx +36 -31
- package/src/plugins/builtin/primary-dealers/client.ts +137 -62
- package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
- package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
- package/src/plugins/builtin/primary-dealers/model.ts +179 -129
- package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
- package/src/plugins/builtin/research/analyst-model.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/client.ts +2 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +4 -3
- package/src/plugins/builtin/risk-factors/index.tsx +12 -3
- package/src/plugins/builtin/scanner/feed.ts +6 -1
- package/src/plugins/builtin/scanner/headless.ts +1 -1
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/client.ts +2 -1
- package/src/plugins/builtin/sectors/client.ts +4 -3
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- package/src/plugins/builtin/shared/cloud-resource.ts +15 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
- package/src/plugins/builtin/shared/free-routes.ts +24 -0
- package/src/plugins/builtin/shared/headless-options.ts +11 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +64 -0
- package/src/plugins/builtin/shared/series-list-detail.tsx +3 -1
- package/src/plugins/builtin/shared/volatility/options.ts +2 -1
- package/src/plugins/builtin/social-mentions/client.ts +2 -1
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -2
- package/src/plugins/builtin/themes/model.ts +43 -10
- package/src/plugins/builtin/themes/pane.tsx +8 -6
- package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
- package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
- package/src/plugins/builtin/time-sales/pane.tsx +2 -2
- package/src/plugins/builtin/trace-bonds/client.ts +14 -3
- package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
- package/src/plugins/builtin/vol-surface/client.ts +23 -3
- package/src/plugins/builtin/vol-surface/headless.ts +33 -14
- package/src/plugins/builtin/vol-surface/model.ts +30 -10
- package/src/plugins/builtin/vol-surface/pane.tsx +35 -15
- package/src/plugins/builtin/vol-surface/stored.ts +34 -3
- package/src/plugins/builtin/vol-surface/tables.ts +14 -0
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/compat.ts +3 -0
- package/src/plugins/external-runtime.ts +5 -0
- package/src/plugins/loader.ts +6 -0
- package/src/plugins/plugin-icon.ts +61 -0
- package/src/plugins/registry/host-actions.ts +2 -1
- package/src/plugins/registry/index.ts +9 -1
- package/src/plugins/runtime/context.tsx +5 -0
- package/src/plugins/runtime/index.tsx +3 -0
- package/src/portfolio/column-values.ts +13 -4
- package/src/portfolio/cost-fx.ts +5 -28
- package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
- package/src/public/broker.ts +8 -0
- package/src/public/market-data.ts +5 -0
- package/src/renderers/dom/desktop/control-styles.ts +7 -1
- package/src/renderers/dom/desktop/controls.tsx +40 -8
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/list-view.tsx +51 -19
- package/src/renderers/dom/desktop/menu.tsx +10 -1
- package/src/renderers/dom/desktop/query-bar.tsx +2 -0
- package/src/renderers/dom/desktop/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +22 -5
- package/src/renderers/dom/host/top-surface.ts +127 -0
- package/src/renderers/dom/styles.css +51 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +21 -1
- package/src/renderers/electrobun/bun/desktop/backend.ts +10 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +210 -30
- package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
- package/src/renderers/electrobun/view/external-plugins.ts +1 -0
- package/src/renderers/electrobun/view/news-source.ts +4 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +8 -1
- package/src/renderers/opentui/start.tsx +7 -0
- package/src/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/index.ts +57 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/provider-errors.ts +3 -1
- package/src/sources/provider-router/history.ts +49 -6
- package/src/sources/provider-router/index.ts +18 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +62 -11
- package/src/sync/local-only.ts +121 -0
- package/src/sync/react.ts +6 -0
- package/src/sync/types.ts +3 -0
- package/src/telemetry/crash-reports.ts +4 -0
- package/src/telemetry/update-failures-node.ts +59 -0
- package/src/telemetry/update-failures.ts +307 -0
- package/src/tickers/company-name-listing.ts +9 -7
- package/src/tickers/company-name.ts +43 -0
- package/src/tickers/listing-fate.ts +57 -8
- package/src/tickers/search/index.ts +24 -3
- package/src/time-series/price-comparison.ts +35 -0
- package/src/time-series/resolve.ts +52 -11
- package/src/time-series/spec.ts +1 -1
- package/src/time-series/types.ts +5 -0
- package/src/types/broker.ts +56 -1
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +11 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +19 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/issuer-listings.ts +28 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/updater/failure.ts +163 -0
- package/src/updater/index.ts +87 -23
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +28 -1
- package/src/utils/currency-units.ts +32 -1
- package/src/utils/dom-screenshot.ts +22 -9
- package/src/utils/exchange-notation.generated.ts +360 -0
- package/src/utils/exchanges.ts +15 -322
- package/src/utils/format.ts +31 -1
- package/src/utils/instrument-identity.ts +38 -1
- package/src/utils/options.ts +46 -1
- package/src/utils/price-history.ts +4 -0
- package/src/utils/reported-money.ts +8 -4
- package/src/utils/sec.ts +68 -0
|
@@ -10,6 +10,7 @@ import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginAppActi
|
|
|
10
10
|
import { usePaneTickerIdentity } from "../../../state/hooks/pane-ticker";
|
|
11
11
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
12
12
|
import { quoteBlockStyle } from "../../../components/ui/quote";
|
|
13
|
+
import { ClippedText } from "../../../components/ui/display";
|
|
13
14
|
import type { PaneProps } from "../../../types/plugin";
|
|
14
15
|
import { Box, Text, useRendererHost, useUiCapabilities, type ScrollBoxRenderable } from "../../../ui";
|
|
15
16
|
import { SignInWall } from "../cloud/auth-actions";
|
|
@@ -20,8 +21,8 @@ import { isCloudSessionRequired, useResearchCloudSession } from "../shared/resea
|
|
|
20
21
|
import { cachedCompanyData, loadCompanyData, KPI_UNAVAILABLE, validateCompanyData, type CompanyDataset, type CompanyMode } from "./client";
|
|
21
22
|
import { useCompanyEvidence } from "./screenshot-evidence";
|
|
22
23
|
import { allGuidance, allObservations, basisLabel, changeText, changeTone, companyCellText, companyColumns, dimensionLabel, directionLabel, directionTone, guideGroupKey, guidanceSeries, fiscalLabel,
|
|
23
|
-
isGuidance, exactObservation, observationValue, observationChange, observationChart, periodOrder, outcomeLabel, QUIET_COLUMNS, rangeText,
|
|
24
|
-
type CompanyRow } from "./model";
|
|
24
|
+
isGuidance, exactObservation, observationValue, observationChange, observationChart, periodOrder, outcomeLabel, QUIET_COLUMNS, rangeText, scopeWords, seriesDetail, seriesLabel, seriesLabels, seriesName,
|
|
25
|
+
seriesOrder, unitLabel, valueText, companyPreviewNote, type CompanyRow } from "./model";
|
|
25
26
|
import { PriceSparkline } from "../../../components/price-sparkline/view";
|
|
26
27
|
import type { PricePoint } from "../../../types/financials";
|
|
27
28
|
import { toneColor, withoutQuietColumns } from "../shared/research-cells";
|
|
@@ -41,7 +42,7 @@ function DisclosureEvidence({ row, sourceId, onSource, width }: { row: CompanyRo
|
|
|
41
42
|
<KeyValueRow label="Value" value={isGuidance(row) ? rangeText(row) : observationValue(row)} detail={row.currency || row.unit === "volume" || row.unit === "count" ? unitLabel(row) : undefined} labelWidth={18} />
|
|
42
43
|
<KeyValueRow label="Fiscal period" value={row.period.label} detail={row.period.end ? row.period.start ? `${row.period.start} to ${row.period.end}` : `${row.period.kind === "instant" ? "As of" : "Ended"} ${row.period.end}` : "Calendar dates unavailable"} labelWidth={18} />
|
|
43
44
|
<KeyValueRow label="Basis" value={basisLabel(row.basis)} labelWidth={18} />
|
|
44
|
-
<KeyValueRow label="Scope" value={dimensionLabel(row.dimensions) || "Consolidated"} labelWidth={18} />
|
|
45
|
+
<KeyValueRow label="Scope" value={(desktop ? scopeWords(seriesLabels(row)) : dimensionLabel(row.dimensions)) || "Consolidated"} labelWidth={18} />
|
|
45
46
|
<KeyValueRow label="Definition" value={row.metric.definition} labelWidth={18} />
|
|
46
47
|
<KeyValueRow label="Revision" value={`${row.revision}${row.current ? " · current" : " · superseded"}${!isGuidance(row) && (row.conflict || row.contested) ? " · conflicting disclosures" : ""}`} labelWidth={18} />
|
|
47
48
|
{row.revisionReason ? <KeyValueRow label="Correction" value={row.revisionReason} labelWidth={18} /> : null}
|
|
@@ -99,22 +100,24 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
|
|
|
99
100
|
const guidance = data && "guidance" in data ? data : null;
|
|
100
101
|
const [openingTab] = usePaneSettingValue("tab", "table");
|
|
101
102
|
const [tabValue, setTab] = usePluginPaneState<string>("company:tab", openingTab);
|
|
102
|
-
const tab = ["table", "chart", "evidence", ...(guidance ? ["history"] : [])].includes(tabValue) ? tabValue : "table";
|
|
103
|
+
const tab = ["table", "chart", "evidence", ...(mode === "guidance" ? ["history"] : [])].includes(tabValue) ? tabValue : "table";
|
|
103
104
|
const [openingMetric] = usePaneSettingValue("metric", "all");
|
|
104
105
|
const [metric, setMetric] = usePluginPaneState<string>("company:metric", openingMetric);
|
|
105
106
|
const [plotKey, setPlotKey] = usePluginPaneState<string>("company:plot", "");
|
|
106
107
|
const [selectedId, setSelected] = usePluginPaneState<string | null>(`company:selected:${tab}`, null);
|
|
107
108
|
const [openId, setOpen] = usePluginPaneState<string | null>("company:open", null);
|
|
108
109
|
const [sourceId, setSource] = usePluginPaneState<string | null>("company:source", null);
|
|
109
|
-
|
|
110
|
+
// The desktop lists KPIs by metric, each series named in plain words (GLO-1046); the terminal keeps its frames.
|
|
111
|
+
const [sort, setSort] = usePluginPaneState<{ column: string; direction: "asc" | "desc" }>("company:sort",
|
|
112
|
+
desktop && mode === "kpis" ? { column: "metric", direction: "asc" } : { column: "period", direction: "desc" });
|
|
110
113
|
const detailScrollRef = useRef<ScrollBoxRenderable | null>(null);
|
|
111
114
|
const observations = useMemo(() => kpis ? allObservations(kpis) : [], [kpis]);
|
|
112
115
|
const guides = useMemo(() => guidance ? allGuidance(guidance) : [], [guidance]);
|
|
113
116
|
const allRows = useMemo<CompanyRow[]>(() => [...observations, ...guides], [observations, guides]);
|
|
114
117
|
const eligible = useMemo(() => allRows.filter((row) => metric === "all" || row.metricId === metric), [allRows, metric]);
|
|
115
118
|
const plotOptions = useMemo(() => mode === "kpis"
|
|
116
|
-
? [...new Map(eligible.filter((row) => !isGuidance(row)).map((row) => [`${row.seriesKey}:${row.period.kind}`, { value: `${row.seriesKey}:${row.period.kind}`, label: `${seriesLabel(row)} · ${row.period.kind}` }])).values()]
|
|
117
|
-
: [...new Map(eligible.filter(isGuidance).map((row) => [guideGroupKey(row), { value: guideGroupKey(row), label: `${seriesLabel(row)} · ${row.period.label}` }])).values()], [eligible, mode]);
|
|
119
|
+
? [...new Map(eligible.filter((row) => !isGuidance(row)).map((row) => [`${row.seriesKey}:${row.period.kind}`, { value: `${row.seriesKey}:${row.period.kind}`, label: `${desktop ? seriesName(row) : seriesLabel(row)} · ${row.period.kind}` }])).values()]
|
|
120
|
+
: [...new Map(eligible.filter(isGuidance).map((row) => [guideGroupKey(row), { value: guideGroupKey(row), label: `${desktop ? seriesName(row) : seriesLabel(row)} · ${row.period.label}` }])).values()], [eligible, mode, desktop]);
|
|
118
121
|
const activePlot = plotOptions.some((option) => option.value === plotKey) ? plotKey : plotOptions[0]?.value ?? "";
|
|
119
122
|
const rows = useMemo(() => {
|
|
120
123
|
const values = eligible.filter((row) => tab === "chart" ? (isGuidance(row) ? guideGroupKey(row) : `${row.seriesKey}:${row.period.kind}`) === activePlot
|
|
@@ -130,11 +133,13 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
|
|
|
130
133
|
: sort.column === "quote" ? row.evidence[0]!.quote : sort.column === "change" ? guide ? guide.direction : observationChange(row as KpiObservation, observations).value
|
|
131
134
|
: periodOrder(row);
|
|
132
135
|
};
|
|
136
|
+
const direction = sort.direction === "asc" ? 1 : -1;
|
|
137
|
+
if (desktop && sort.column === "metric") return [...values].sort((a, b) => (seriesOrder(a, b) || periodOrder(b).localeCompare(periodOrder(a)) || a.id.localeCompare(b.id)) * direction);
|
|
133
138
|
return [...values].sort((a, b) => { const x = key(a), y = key(b);
|
|
134
139
|
return x === null || y === null ? x === y ? a.id.localeCompare(b.id) : x === null ? 1 : -1
|
|
135
|
-
: (x < y ? -1 : x > y ? 1 : a.metric.name.localeCompare(b.metric.name) || dimensionLabel(a.dimensions).localeCompare(dimensionLabel(b.dimensions)) || a.id.localeCompare(b.id)) *
|
|
140
|
+
: (x < y ? -1 : x > y ? 1 : a.metric.name.localeCompare(b.metric.name) || dimensionLabel(a.dimensions).localeCompare(dimensionLabel(b.dimensions)) || a.id.localeCompare(b.id)) * direction;
|
|
136
141
|
});
|
|
137
|
-
}, [eligible, tab, activePlot, guidance, kpis, sort, observations]);
|
|
142
|
+
}, [eligible, tab, activePlot, guidance, kpis, sort, observations, desktop]);
|
|
138
143
|
const selected = rows.find((row) => row.id === selectedId) ?? rows[0];
|
|
139
144
|
const openRow = allRows.find((row) => row.id === openId);
|
|
140
145
|
const activeRow = openRow ?? selected;
|
|
@@ -153,15 +158,16 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
|
|
|
153
158
|
}
|
|
154
159
|
return map;
|
|
155
160
|
}, [kpis]);
|
|
156
|
-
const columns = useMemo(() => withoutQuietColumns(companyColumns(mode === "guidance", tab === "evidence", tab === "history", mode === "kpis" && tab === "table" && trends.size > 0 && width >= 100),
|
|
157
|
-
rows, (row, id) => id === "basis" ? basisLabel(row.basis) : id === "scope" ? dimensionLabel(row.dimensions) || "Consolidated" : "", QUIET_COLUMNS), [mode, tab, trends, rows, width]);
|
|
161
|
+
const columns = useMemo(() => withoutQuietColumns(companyColumns(mode === "guidance", tab === "evidence", tab === "history", mode === "kpis" && tab === "table" && trends.size > 0 && width >= 100, desktop),
|
|
162
|
+
rows, (row, id) => id === "basis" ? basisLabel(row.basis) : id === "scope" ? dimensionLabel(row.dimensions) || "Consolidated" : "", QUIET_COLUMNS), [mode, tab, trends, rows, width, desktop]);
|
|
158
163
|
const metricOptions = useMemo(() => [{ value: "all", label: "All metrics" }, ...[...new Map(allRows.map((row) => [row.metricId, { value: row.metricId, label: row.metric.name }])).values()]], [allRows]);
|
|
159
164
|
const kpiChart = useMemo(() => observationChart(rows.filter((row): row is KpiObservation => !isGuidance(row)), colors.warning), [rows, colors.warning]);
|
|
160
165
|
const series = useMemo(() => tab !== "chart" ? [] : mode === "kpis" ? kpiChart.series
|
|
161
166
|
: guidanceSeries(guides, selected && isGuidance(selected) ? selected : undefined, { low: colors.textMuted, high: colors.warning, actual: colors.positive }), [tab, mode, kpiChart, guides, selected, colors]);
|
|
162
167
|
const link = useChartTableSelection({ rows, getId: (row) => row.id, getDate: (row) => isGuidance(row) ? new Date(row.issuedDate) : kpiChart.dates.get(row.id) ?? null,
|
|
163
168
|
selectedId: selected?.id ?? null, onSelect: setSelected, focused: focused && !openRow, enabled: tab === "chart" });
|
|
164
|
-
const
|
|
169
|
+
const signInWall = !data && isCloudSessionRequired(resource.error);
|
|
170
|
+
const { strip, rows: tabRows } = usePaneTabs(symbol && !signInWall ? { tabs: [{ value: "table", label: mode === "guidance" ? "Tracker" : "Table" }, { value: "chart", label: "Chart" },
|
|
165
171
|
...(mode === "guidance" ? [{ value: "history", label: "History" }] : []), { value: "evidence", label: "Evidence" }], activeValue: tab,
|
|
166
172
|
onSelect: (value) => { setTab(value); setOpen(null); }, focused, dense: true } : null);
|
|
167
173
|
useAutoRefresh(resource.updatedAt, resource.load);
|
|
@@ -185,12 +191,12 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
|
|
|
185
191
|
data?.truncated && data.access === "full" ? "The response reached its stored-record limit. Narrow the metric or date range in a CLI or REST query." : null,
|
|
186
192
|
data?.coverage.conflicts ? "Conflicting disclosures are retained in Evidence and excluded from comparable trends." : null].filter((value): value is string => !!value) });
|
|
187
193
|
if (!symbol) return <EmptyState title="Select a ticker." />;
|
|
188
|
-
if (
|
|
194
|
+
if (signInWall) return <SignInWall placement="company-kpis-signin" action={mode === "guidance" ? "view company guidance" : "view company KPIs"} needsVerification={session.needsVerification} />;
|
|
189
195
|
const bodyHeight = Math.max(3, height - tabRows);
|
|
190
196
|
const query = <QueryBar width={width} filters={[{ id: "metric", label: "Metric", value: metric, defaultValue: "all", options: metricOptions,
|
|
191
197
|
onChange: (value: string) => { setMetric(value); setSelected(null); setOpen(null); } }, ...(tab === "chart" && plotOptions.length ? [{ id: "series", label: "Series", value: activePlot,
|
|
192
198
|
options: plotOptions, onChange: (value: string) => { setPlotKey(value); setSelected(null); } }] : [])]} />;
|
|
193
|
-
const renderCell = (item: Item, column: (typeof columns)[number]): DataTableCell => {
|
|
199
|
+
const renderCell = (item: Item, column: (typeof columns)[number], _index: number, state: { selected: boolean }): DataTableCell => {
|
|
194
200
|
const row = item.row;
|
|
195
201
|
if (!row) {
|
|
196
202
|
if (isNote(item)) return { text: "" };
|
|
@@ -210,6 +216,15 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
|
|
|
210
216
|
const change = observationChange(row as KpiObservation, observations);
|
|
211
217
|
return { text: changeText(change), value: change.value, color: toneColor(changeTone(row as KpiObservation, change.value), colors) };
|
|
212
218
|
}
|
|
219
|
+
if (desktop && column.id === "metric") {
|
|
220
|
+
// The series in plain words: the metric bright, what tells it apart dim. The detail starts from no width and takes
|
|
221
|
+
// what the name leaves, so it gives way first and the name is cut only when it alone does not fit.
|
|
222
|
+
const detail = seriesDetail(row);
|
|
223
|
+
return { text: detail ? `${row.metric.name} ${detail}` : row.metric.name, content: <Box flexDirection="row" height={1} overflow="hidden" flexShrink={1} minWidth={0}>
|
|
224
|
+
<ClippedText fg={state.selected ? colors.selectedText : colors.textBright} text={row.metric.name} width={column.width} />
|
|
225
|
+
{detail ? <ClippedText fg={state.selected ? colors.selectedText : colors.textDim} lead=" " text={detail} width={column.width} flex="1 1 0%" /> : null}
|
|
226
|
+
</Box> };
|
|
227
|
+
}
|
|
213
228
|
const text = companyCellText(row, column.id, observations);
|
|
214
229
|
return { text, ...(column.id === "value" && !guide ? { value: exactObservation(row as KpiObservation) ? (row as KpiObservation).value : observationValue(row as KpiObservation) } : column.id === "actual" ? { value: guide?.actual?.value } : {}),
|
|
215
230
|
color: column.id === "metric" ? colors.textBright : column.id === "value" || column.id === "actual" ? colors.textBright
|
|
@@ -225,7 +240,7 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
|
|
|
225
240
|
selection={{ kind: "id", selectedId: selected?.id ?? null, getId, onChange: setSelected }}
|
|
226
241
|
onActivate={(item) => item.row ? setOpen(item.row.id) : isNote(item) ? undefined : openUpgrade()} renderCell={renderCell}
|
|
227
242
|
isNavigable={(item) => !isNote(item)} renderSectionHeader={(item) => "note" in item && item.note ? { text: item.note } : null} freezeFirstColumn selectedTextOverridesCellColor showHorizontalScrollbar
|
|
228
|
-
detailOpen={!!openRow} onBack={() => setOpen(null)} detailTitle={openRow ? `${openRow.metric.name} · ${fiscalLabel(openRow)}` : undefined}
|
|
243
|
+
detailOpen={!!openRow} onBack={() => setOpen(null)} detailTitle={openRow ? `${desktop ? seriesName(openRow) : openRow.metric.name} · ${fiscalLabel(openRow)}` : undefined}
|
|
229
244
|
detailScrollRef={detailScrollRef} detailContent={openRow ? <DetailScrollBody ref={detailScrollRef} resetScrollKey={openRow.id}><DisclosureEvidence row={openRow} sourceId={sourceId} onSource={setSource} width={width} /></DetailScrollBody> : null}
|
|
230
245
|
sortColumnId={sort.column} sortDirection={sort.direction} onHeaderClick={(column) => setSort((previous) => ({ column, direction: previous.column === column && previous.direction === "desc" ? "asc" : "desc" }))}
|
|
231
246
|
resetScrollKey={`${mode}:${symbol}:${tab}:${metric}:${activePlot}`}
|
|
@@ -10,6 +10,7 @@ import { supplyChainModule } from "./supply-chain";
|
|
|
10
10
|
import { awardsModule } from "./awards";
|
|
11
11
|
import { cryptoBoardModule } from "./crypto-board";
|
|
12
12
|
import { portfolioAnalyticsModule } from "./analytics";
|
|
13
|
+
import { brokerTradingModule } from "./broker-trading";
|
|
13
14
|
import { brokerManagerModule } from "./broker-manager";
|
|
14
15
|
import { changelogModule } from "./changelog";
|
|
15
16
|
import { connectionsModule } from "./connections";
|
|
@@ -189,9 +190,9 @@ export const brokerPlugin = composeBuiltinPlugin({
|
|
|
189
190
|
id: "broker",
|
|
190
191
|
name: "Broker",
|
|
191
192
|
version: "1.0.0",
|
|
192
|
-
description: "Broker profiles, account sync, and
|
|
193
|
+
description: "Broker profiles, account sync, order review, and trading status.",
|
|
193
194
|
toggleable: true,
|
|
194
|
-
modules: [brokerManagerModule],
|
|
195
|
+
modules: [brokerManagerModule, brokerTradingModule],
|
|
195
196
|
});
|
|
196
197
|
|
|
197
198
|
export const globalMarketsPlugin = composeBuiltinPlugin({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { HeadlessPaneDefinition } from "../../../types/headless";
|
|
2
2
|
import type { TimeRange } from "../../../time-series/range";
|
|
3
3
|
import { formatNumber, formatSignificant } from "../../../utils/format";
|
|
4
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
4
5
|
import { mixedSessionCloseNote } from "../../../market-data/market/session-close-note";
|
|
5
6
|
import { resolveHeadlessInstrument, loadHeadlessSymbols } from "../shared/headless-market-data";
|
|
6
7
|
import { buildCorrelationMatrix, buildCorrelationSeries, buildGeoCorrelationSeries, pairKey, type CorrelationSeries } from "./matrix/model";
|
|
@@ -27,7 +28,7 @@ async function loadListedHistory(ctx: HeadlessPaneContext, key: string, range: T
|
|
|
27
28
|
const { symbol, exchange } = await resolveHeadlessInstrument(ctx, key);
|
|
28
29
|
const [points, quote] = await Promise.all([
|
|
29
30
|
loadHistory(ctx, key, range),
|
|
30
|
-
exchange ? null : (async () => ctx.marketData.getQuote(symbol, "", ctx.refresh ? { cacheMode: "refresh" } : undefined))().catch((
|
|
31
|
+
exchange ? null : (async () => ctx.marketData.getQuote(symbol, "", ctx.refresh ? { cacheMode: "refresh" } : undefined))().catch(logFailure("correlation", "Listing exchange quote failed", null)),
|
|
31
32
|
]);
|
|
32
33
|
return { exchange: exchange || quote?.listingExchangeName || quote?.exchangeName || "", points };
|
|
33
34
|
}
|
|
@@ -242,6 +242,7 @@ export function relationshipTemplateSymbols(
|
|
|
242
242
|
try {
|
|
243
243
|
return normalizeRelationshipSymbols(parseTickerListInput(raw));
|
|
244
244
|
} catch {
|
|
245
|
+
// Intentional ignore: invalid template input disables creation through canCreate and creates no pane.
|
|
245
246
|
return null;
|
|
246
247
|
}
|
|
247
248
|
}
|
|
@@ -34,6 +34,7 @@ function coerceStoredSymbols(value: unknown): string[] {
|
|
|
34
34
|
try {
|
|
35
35
|
return parseCorrelationSymbolsInput(text, MAX_CORRELATION_TICKERS);
|
|
36
36
|
} catch {
|
|
37
|
+
// Intentional ignore: malformed legacy symbols fall back to saved text or the default preset.
|
|
37
38
|
return [];
|
|
38
39
|
}
|
|
39
40
|
}
|
|
@@ -29,7 +29,7 @@ export const cotHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
29
29
|
shape: "bundle",
|
|
30
30
|
// Tuesday positions published the Friday after; two weeks without a new report means one was missed.
|
|
31
31
|
freshness: { source: "CFTC", status: "not-a-feed", basis: "weekly report", maxAgeMinutes: 14 * 24 * 60 },
|
|
32
|
-
argument: { kind: "free-text", optional: true, placeholder: "code or
|
|
32
|
+
argument: { kind: "free-text", optional: true, placeholder: "code or name", description: "Futures root (KC), commodity name (coffee) or CFTC market code; omit for the extremes board." },
|
|
33
33
|
options: [{ key: "report", type: "enum", values: [{ value: "legacy" }, { value: "disaggregated" }], defaultValue: "legacy", description: "CFTC report family." },
|
|
34
34
|
{ key: "traderClass", type: "enum", values: ["noncommercial", "commercial", "producer", "swap", "managed-money", "other-reportable", "nonreportable"].map((value) => ({ value })), description: "Trader class for the extremes board; defaults by report family.",
|
|
35
35
|
pluginState: { pluginId: COMMODITIES_VIEW_STATE.cot, key: "class" } },
|
|
@@ -13,7 +13,7 @@ export const cotModule: PluginModule = {
|
|
|
13
13
|
paneTemplates: [{ id: "cot-cot", paneId: "cot", label: "CFTC Positioning",
|
|
14
14
|
description: "Weekly futures positioning, changes, historical percentiles and cross-market extremes.",
|
|
15
15
|
keywords: ["cot", "cftc", "positioning", "commitments", "managed money", "net spec", "crude oil", "natural gas"],
|
|
16
|
-
shortcut: { prefix: "COT", aliases: ["CFTC"], argKind: "text" as const, argPlaceholder: "code or
|
|
16
|
+
shortcut: { prefix: "COT", aliases: ["CFTC"], argKind: "text" as const, argPlaceholder: "code or name", argOptional: true,
|
|
17
17
|
argOptions: () => COT_ROOT_OPTIONS }, headless: cotHeadless,
|
|
18
18
|
createInstance: (context, options) => {
|
|
19
19
|
const input = options?.arg?.trim();
|
|
@@ -5,7 +5,7 @@ import type { PricePoint } from "../../../types/financials";
|
|
|
5
5
|
import { formatPriceObservation } from "../../../market-data/market/format";
|
|
6
6
|
import { cleanFloat32Price } from "../../../cli/history-rows";
|
|
7
7
|
import { cryptoPairCoin, isFuturesSymbol } from "../shared/crypto-pair";
|
|
8
|
-
import { FUTURES_CONTRACTS, futuresBoardContract, futuresBoardContractForTicker, type FuturesContract } from "../../../market-data/futures-board";
|
|
8
|
+
import { bestNameMatch, FUTURES_CONTRACTS, futuresBoardContract, futuresBoardContractForTicker, futuresContractByName, type FuturesContract } from "../../../market-data/futures-board";
|
|
9
9
|
|
|
10
10
|
export const COT_CLASSES: Record<CotFamily, Array<{ value: CotClass; label: string }>> = {
|
|
11
11
|
legacy: [{ value: "noncommercial", label: "Noncommercial" }, { value: "commercial", label: "Commercial" }, { value: "nonreportable", label: "Nonreportable" }],
|
|
@@ -124,25 +124,48 @@ export function cotScope(value: unknown): CotScope { return value === "all" ? "a
|
|
|
124
124
|
const rootAlias = (input: string) => ROOTS[input === "VIX" ? "VX" : input]?.code ?? null;
|
|
125
125
|
const marketCode = (input: string) => /^[0-9A-Z]{5}[0-9A-Z+]$/.test(input) ? input : null;
|
|
126
126
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
127
|
+
* The FUT board row a typed argument names, by code, symbol or commodity name
|
|
128
|
+
* as FUT reads them (CC, CC=F, cocoa), or a verified root named the same way
|
|
129
|
+
* (bitcoin, VIX, SOFR, fed funds).
|
|
130
|
+
*/
|
|
131
|
+
function typedMarket(value: string): { board: FuturesContract } | { root: string } | null {
|
|
132
|
+
const board = futuresBoardContract(value) ?? futuresContractByName(value);
|
|
133
|
+
if (board) return { board };
|
|
134
|
+
const root = bestNameMatch(Object.keys(ROOTS), (key) => [ROOTS[key]!.name], value);
|
|
135
|
+
return root ? { root } : null;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* A CFTC code, root or commodity name typed as an argument or stored in a
|
|
139
|
+
* pane: every alias, crypto included, every FUT board root the CFTC reports,
|
|
140
|
+
* and the names FUT finds them by (cocoa, coffee, sugar, gold, crude). A typed
|
|
141
|
+
* root is always the future, so ZM here is soybean meal, never Zoom. A CFTC
|
|
142
|
+
* code is tried after the names, as one could be spelled with six letters
|
|
143
|
+
* (COFFEE, COPPER).
|
|
130
144
|
*/
|
|
131
145
|
export function cotContractCode(value: unknown): string | null {
|
|
132
146
|
if (typeof value !== "string") return null;
|
|
133
147
|
const input = value.trim().toUpperCase().replace(/=F$/, "");
|
|
134
|
-
const
|
|
135
|
-
|
|
148
|
+
const alias = rootAlias(input);
|
|
149
|
+
if (alias) return alias;
|
|
150
|
+
const market = typedMarket(value);
|
|
151
|
+
if (market && "root" in market) return ROOTS[market.root]!.code;
|
|
152
|
+
return (market ? boardMarketCode(market.board) : null) ?? (market ? null : marketCode(input));
|
|
136
153
|
}
|
|
154
|
+
/** The markets the unknown-market hint names, by the code and the word a newcomer knows them by. */
|
|
155
|
+
const SUGGESTED_MARKETS: ReadonlyArray<readonly [string, string]> = [
|
|
156
|
+
["ES", "S&P 500"], ["CL", "crude oil"], ["NG", "natural gas"], ["GC", "gold"], ["HG", "copper"],
|
|
157
|
+
["ZC", "corn"], ["CC", "cocoa"], ["KC", "coffee"], ["SB", "sugar"], ["ZN", "10-year note"],
|
|
158
|
+
];
|
|
137
159
|
/**
|
|
138
|
-
* What a market `cotContractCode` does not know is told: a FUT board
|
|
139
|
-
* CFTC does not report says so; anything else gets
|
|
160
|
+
* What a market `cotContractCode` does not know is told: a FUT board row the
|
|
161
|
+
* CFTC does not report says so; anything else gets codes and names that work,
|
|
162
|
+
* softs and metals among them, and a CFTC code.
|
|
140
163
|
*/
|
|
141
164
|
export function cotUnknownMarketMessage(value: unknown): string {
|
|
142
|
-
const
|
|
143
|
-
if (board) return `${board.code} (${board.name}) is not reported in the CFTC COT.`;
|
|
144
|
-
const
|
|
145
|
-
return `Unknown market "${String(value)}". Try ${
|
|
165
|
+
const market = typeof value === "string" ? typedMarket(value) : null;
|
|
166
|
+
if (market && "board" in market) return `${market.board.code} (${market.board.name}) is not reported in the CFTC COT.`;
|
|
167
|
+
const known = SUGGESTED_MARKETS.map(([code, name]) => `${code} ${name}`).join(", ");
|
|
168
|
+
return `Unknown market "${String(value)}". Try a code or name such as ${known}, or a CFTC market code such as ${ROOTS.GC!.code}.`;
|
|
146
169
|
}
|
|
147
170
|
/**
|
|
148
171
|
* The code for the ticker under the cursor, when COT is opened without an
|
|
@@ -82,8 +82,8 @@ function CotBoard({ width, height, focused, family, initialCode }: PaneProps & {
|
|
|
82
82
|
}, [data, query, scope, sort]);
|
|
83
83
|
useAutoRefresh(resource.updatedAt, resource.load);
|
|
84
84
|
// The class, scope and search controls live in the board's query bar, so
|
|
85
|
-
// their keys
|
|
86
|
-
const boardShown =
|
|
85
|
+
// their keys work while it is on screen: over the board and while a class loads.
|
|
86
|
+
const boardShown = !open;
|
|
87
87
|
useShortcut((event) => {
|
|
88
88
|
if (!focused || open || event.targetEditable || event.ctrl || event.meta || event.alt || event.super) return;
|
|
89
89
|
if (event.name === "r") { event.preventDefault(); void resource.reload(); }
|
|
@@ -99,8 +99,9 @@ function CotBoard({ width, height, focused, family, initialCode }: PaneProps & {
|
|
|
99
99
|
usePaneNoticeFooter({ registrationId: "cot:board-notices", focused, enabled: !open, notices: data?.gaps ?? [] });
|
|
100
100
|
usePaneStatusFooter({ registrationId: "cot:board", enabled: !open, loading: resource.loading, error: resource.error,
|
|
101
101
|
info: data ? [{ id: "as-of", parts: [{ text: `CFTC futures only · ${data.asOf ?? "--"} · contracts`, tone: "muted" }] }] : [] });
|
|
102
|
-
|
|
102
|
+
// A class change reloads under the query bar, which stays.
|
|
103
103
|
return <DataTableStackView<CotBoardRow> columns={BOARD_COLUMNS} items={rows} focused={focused && !searching}
|
|
104
|
+
status={data ? undefined : { loading: resource.loading, error: resource.error, empty: !resource.loading && !resource.error, subject: "COT history" }}
|
|
104
105
|
rootWidth={width} rootHeight={height} selection={{ kind: "id", selectedId: selected, getId: (row) => row.contractCode, onChange: setSelected }}
|
|
105
106
|
getItemKey={(row) => row.contractCode} onActivate={(row) => setOpen(row.contractCode)} freezeFirstColumn
|
|
106
107
|
sortable sortColumnId={sort.columnId} sortDirection={sort.direction} onHeaderClick={(id) => setSort((current) => nextHeaderSort(current, id, { firstDirection: "desc" }))}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import {
|
|
3
|
+
DELAYED_SEGMENT,
|
|
3
4
|
ChartTableHeader,
|
|
4
5
|
CompositeChart,
|
|
5
6
|
MarketBoardStack,
|
|
@@ -107,7 +108,7 @@ export function CdxPane({ focused, width, height }: PaneProps) {
|
|
|
107
108
|
const asOf = formatAsOf(payload?.asOf ?? null);
|
|
108
109
|
const info = useMemo<PaneFooterSegment[]>(() => [
|
|
109
110
|
...(asOf ? [{ id: "as-of", parts: [{ text: `as of ${asOf}`, tone: "muted" as const }] }] : []),
|
|
110
|
-
...(payload ? [
|
|
111
|
+
...(payload ? [DELAYED_SEGMENT] : []),
|
|
111
112
|
...(resource.data?.stale ? [{ id: "stale", parts: [{ text: "STALE", tone: "warning" as const }] }] : []),
|
|
112
113
|
], [asOf, payload, resource.data?.stale]);
|
|
113
114
|
usePaneStatusFooter({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useMemo } from "react";
|
|
2
2
|
import {
|
|
3
|
+
DELAYED_SEGMENT,
|
|
3
4
|
ChartTableHeader,
|
|
4
5
|
CompositeChart,
|
|
5
6
|
formatBpAxis,
|
|
@@ -112,7 +113,7 @@ export function SovrPane({ focused, width, height }: PaneProps) {
|
|
|
112
113
|
const asOf = formatAsOf(payload?.asOf ?? null);
|
|
113
114
|
const info = useMemo<PaneFooterSegment[]>(() => [
|
|
114
115
|
...(asOf ? [{ id: "as-of", parts: [{ text: `as of ${asOf}`, tone: "muted" as const }] }] : []),
|
|
115
|
-
...(payload ? [
|
|
116
|
+
...(payload ? [DELAYED_SEGMENT] : []),
|
|
116
117
|
...(resource.data?.stale ? [{ id: "stale", parts: [{ text: "STALE", tone: "warning" as const }] }] : []),
|
|
117
118
|
], [asOf, payload, resource.data?.stale]);
|
|
118
119
|
usePaneStatusFooter({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useMemo } from "react";
|
|
2
2
|
import {
|
|
3
|
+
DELAYED_SEGMENT,
|
|
3
4
|
ChartTableHeader,
|
|
4
5
|
CompositeChart,
|
|
5
6
|
formatBpAxis,
|
|
@@ -115,7 +116,7 @@ export function CreditConditionsPane({ paneId, focused, width, height }: PanePro
|
|
|
115
116
|
notices: mixedDates ? ["The indexes carry different observation dates, so the spreads are not one session."] : [],
|
|
116
117
|
});
|
|
117
118
|
const footerInfo = useMemo<PaneFooterSegment[]>(() => [
|
|
118
|
-
...(rows.length > 0 ? [
|
|
119
|
+
...(rows.length > 0 ? [DELAYED_SEGMENT] : []),
|
|
119
120
|
...(partial ? [{ id: "partial", parts: [{ text: `PARTIAL ${rows.length}/${series.length}`, tone: "warning" as const, bold: true }] }] : []),
|
|
120
121
|
...(stale ? [{ id: "stale", parts: [{ text: "STALE", tone: "warning" as const }] }] : []),
|
|
121
122
|
...loadingErrorFooterInfo(loading, error),
|
|
@@ -2,7 +2,7 @@ import { useCallback, useMemo } from "react";
|
|
|
2
2
|
import { isAccessDenied } from "../../../api-client/errors";
|
|
3
3
|
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
4
4
|
import type { CreditDocumentsPayload, CreditHeadroom, CreditInstrument, CreditScreenPayload, CreditScreenRow } from "../../../api-client/credit-documents";
|
|
5
|
-
import { ChartTableHeader, DataTableView, EmptyState, PageStackView,
|
|
5
|
+
import { ChartTableHeader, DataTableView, EmptyState, PageStackView, QueryBar, useChartTableSelection, usePaneFooter, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, type DataTableCell, type DataTableColumn, type PaneHint } from "../../../components";
|
|
6
6
|
import { formatCompactAxis } from "../../../components/chart-table";
|
|
7
7
|
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
8
8
|
import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginAppActions, usePluginPaneState } from "../../../public/react";
|
|
@@ -170,18 +170,19 @@ function CreditView({ symbol, width, height, focused, accessKey, needsVerificati
|
|
|
170
170
|
return { text, value, color: column.id === columns[0]!.id ? colors.textBright : colors.text };
|
|
171
171
|
};
|
|
172
172
|
return <Box width={width} height={height} flexDirection="column">{strip}
|
|
173
|
-
<
|
|
174
|
-
empty={!available && currentError === CREDIT_UNAVAILABLE} emptyTitle={CREDIT_UNAVAILABLE} subject="credit documents">
|
|
175
|
-
{available ? <PageStackView focused={focused && !openFactId} detailOpen={!!openInstrument} onBack={() => setOpen(null)} detailTitle={openInstrument?.name}
|
|
173
|
+
<PageStackView focused={focused && !openFactId} detailOpen={!!openInstrument} onBack={() => setOpen(null)} detailTitle={openInstrument?.name}
|
|
176
174
|
detailContent={openInstrument && activeSymbol ? <CreditInstrumentDetail key={`${activeSymbol}:${openInstrument.id}`} symbol={activeSymbol} instrument={openInstrument} width={width} height={bodyHeight - 1} focused={focused} accessKey={accessKey} view={view} setView={setView} covenant={activeCovenant} openId={openFactId} setOpen={setOpenFact} captured={instrumentSnapshot?.id === openInstrument.id ? instrumentSnapshot : null} pro={data?.access === "full"} onUpgrade={openUpgrade} /> : null}
|
|
177
|
-
rootContent={<DataTableView<Row> columns={columns} items={
|
|
175
|
+
rootContent={<DataTableView<Row> columns={columns} items={available ? items : []}
|
|
176
|
+
// The Screen tab and its thresholds load under the query bar, which stays.
|
|
177
|
+
status={available ? undefined : { loading, error: currentError !== CREDIT_UNAVAILABLE ? currentError : null,
|
|
178
|
+
empty: currentError === CREDIT_UNAVAILABLE, emptyTitle: CREDIT_UNAVAILABLE, subject: "credit documents" }}
|
|
179
|
+
sortColumnId={sort.column} sortDirection={sort.direction} onHeaderClick={(column) => setSort((current) => ({column,direction:current.column === column && current.direction === "asc" ? "desc" : "asc"}))} focused={focused && !openInstrument} rootWidth={width} rootHeight={bodyHeight} rootBefore={rootBefore}
|
|
178
180
|
getItemKey={rowId} selection={{ kind: "id", selectedId: selected?.id ?? null, getId: rowId, onChange: setSelected }}
|
|
179
181
|
onActivate={(row) => row.locked ? openUpgrade() : setOpen(row.screen ? row.id : row.instrumentId ?? null)}
|
|
180
182
|
renderCell={renderCell}
|
|
181
183
|
emptyStateTitle={tab === "screen" ? "No supported credit signals match these thresholds." : tab === "covenants" ? "No financial maintenance covenants documented." : tab === "maturities" ? "No supported instrument maturity amounts." : CREDIT_UNAVAILABLE}
|
|
182
184
|
emptyStateHint={tab === "screen" && (headroom < 100 || months < 36) ? "Raise either threshold to widen the screen." : undefined}
|
|
183
185
|
selectedTextOverridesCellColor showHorizontalScrollbar freezeFirstColumn resetScrollKey={`${symbol}:${tab}:${selectedCurrency}`}
|
|
184
|
-
bodyAfter={locked > 0 && desktop ? <LockedOverlay rows={Math.min(3, locked)} text={lockedText} onPress={openUpgrade} /> : undefined} />} />
|
|
185
|
-
</PaneStatusBody>
|
|
186
|
+
bodyAfter={locked > 0 && desktop ? <LockedOverlay rows={Math.min(3, locked)} text={lockedText} onPress={openUpgrade} /> : undefined} />} />
|
|
186
187
|
</Box>;
|
|
187
188
|
}
|
|
@@ -84,7 +84,34 @@ function withValidStablecoinFields(asset: CryptoMarketAsset): CryptoMarketAsset
|
|
|
84
84
|
return { ...asset, ...(peg ? { peg: null } : {}), ...(range ? { range24h: null } : {}) };
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
const DAY_MS = 86_400_000;
|
|
88
|
+
/** How many UTC days old a coin's latest daily close may be for its previous close to stand, as Gloom Cloud judges a coin quote's. */
|
|
89
|
+
const PREVIOUS_CLOSE_MAX_AGE_DAYS = 2;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A coin of a board from before Gloom Cloud measured its moves since 00:00
|
|
93
|
+
* UTC (a server not yet updated, a copy cached from one), whose change runs
|
|
94
|
+
* over the screener's 24 hours: moved from its previous close instead, the
|
|
95
|
+
* close of its last UTC day while its daily closes reach the last two days.
|
|
96
|
+
* Otherwise that close may be weeks old (Toncoin on 18 Oct 2026), and the
|
|
97
|
+
* coin has no move.
|
|
98
|
+
*/
|
|
99
|
+
function onUtcDayBasis(asset: CryptoMarketAsset, now: number): CryptoMarketAsset {
|
|
100
|
+
const history = asset.history;
|
|
101
|
+
const last = history?.closes.findLastIndex((close) => close != null) ?? -1;
|
|
102
|
+
const lastDay = history && last >= 0 ? Date.parse(`${history.start}T00:00:00Z`) / DAY_MS + last : null;
|
|
103
|
+
const holds = lastDay != null && asset.previousClose != null && asset.previousClose > 0
|
|
104
|
+
&& Math.floor(now / DAY_MS) - lastDay <= PREVIOUS_CLOSE_MAX_AGE_DAYS;
|
|
105
|
+
const change = holds ? asset.price - asset.previousClose! : null;
|
|
106
|
+
return {
|
|
107
|
+
...asset,
|
|
108
|
+
previousClose: holds ? asset.previousClose : null,
|
|
109
|
+
change,
|
|
110
|
+
changePercent: change != null ? (change / asset.previousClose!) * 100 : null,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function validateCryptoMarkets(data: CryptoMarketsPayload, now = Date.now()): CryptoMarketsPayload {
|
|
88
115
|
if (
|
|
89
116
|
!data
|
|
90
117
|
|| data.version !== 1
|
|
@@ -103,7 +130,11 @@ export function validateCryptoMarkets(data: CryptoMarketsPayload): CryptoMarkets
|
|
|
103
130
|
) {
|
|
104
131
|
throw new Error("The server returned an invalid crypto board");
|
|
105
132
|
}
|
|
106
|
-
const
|
|
133
|
+
const legacy = data.dayChangeBasis !== "utc-day";
|
|
134
|
+
const assets = data.assets.map((asset) => {
|
|
135
|
+
const valid = withValidStablecoinFields(asset);
|
|
136
|
+
return legacy ? onUtcDayBasis(valid, now) : valid;
|
|
137
|
+
});
|
|
107
138
|
return assets.every((asset, index) => asset === data.assets[index]) ? data : { ...data, assets };
|
|
108
139
|
}
|
|
109
140
|
|
|
@@ -7,6 +7,7 @@ import { fetchCryptoMarkets, fetchStablecoinSupply } from "./client";
|
|
|
7
7
|
import {
|
|
8
8
|
buildCryptoRows,
|
|
9
9
|
CRYPTO_BOARD_FRESHNESS,
|
|
10
|
+
CRYPTO_CHANGE_BASIS_LABEL,
|
|
10
11
|
formatCryptoPercent,
|
|
11
12
|
formatDayRangeTime,
|
|
12
13
|
formatOffPeg,
|
|
@@ -30,7 +31,9 @@ const COIN_COLUMNS: HeadlessPaneColumn[] = [
|
|
|
30
31
|
{ key: "code", header: "Coin" },
|
|
31
32
|
{ key: "name", header: "Name" },
|
|
32
33
|
{ key: "price", header: "Price", align: "right", format: (_value, row) => String(row.priceText ?? "—") },
|
|
33
|
-
|
|
34
|
+
// Since 00:00 UTC, the day `quote` gives a coin (CRYPTO_CHANGE_BASIS_LABEL): the text says so, as the pane's
|
|
35
|
+
// CHG% UTC does, and an export keeps the header it has always had.
|
|
36
|
+
{ key: "changePercent", header: "Chg %", textHeader: "Chg % UTC", align: "right", format: percent },
|
|
34
37
|
{ key: "return7d", header: "7D %", align: "right", format: percent },
|
|
35
38
|
{ key: "return30d", header: "30D %", align: "right", format: percent },
|
|
36
39
|
{ key: "return1y", header: "1Y %", align: "right", format: percent },
|
|
@@ -167,7 +170,7 @@ export const cryptoBoardHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
167
170
|
pegType: row.pegType,
|
|
168
171
|
} : {}),
|
|
169
172
|
}));
|
|
170
|
-
const metadata = { list: kind, status: data.status, asOf: data.asOf, generatedAt: data.generatedAt };
|
|
173
|
+
const metadata = { list: kind, status: data.status, asOf: data.asOf, generatedAt: data.generatedAt, changeBasis: CRYPTO_CHANGE_BASIS_LABEL };
|
|
171
174
|
if (kind !== "stablecoin") return { rows, errors: data.warnings, metadata };
|
|
172
175
|
const summary = supply ? supplySummaryLine(supply) : null;
|
|
173
176
|
const history = supply ? supplyHistoryTable(supply) : null;
|
|
@@ -57,6 +57,11 @@ export interface CryptoRow extends CryptoRowSupply {
|
|
|
57
57
|
price: number;
|
|
58
58
|
/** The price as the board prints it. */
|
|
59
59
|
priceText: string;
|
|
60
|
+
/**
|
|
61
|
+
* The move since 00:00 UTC, from the previous UTC day's close, as `quote`
|
|
62
|
+
* measures a coin (CRYPTO_CHANGE_BASIS_LABEL); null when the coin has no
|
|
63
|
+
* such close to measure from.
|
|
64
|
+
*/
|
|
60
65
|
changePercent: number | null;
|
|
61
66
|
return7d: number | null;
|
|
62
67
|
return30d: number | null;
|
|
@@ -100,9 +105,28 @@ function closeDaysAgo(asset: CryptoMarketAsset, days: number, now: number): numb
|
|
|
100
105
|
return isFiniteNumber(close) && close > 0 ? close : null;
|
|
101
106
|
}
|
|
102
107
|
|
|
103
|
-
const percentChange = (price: number, reference: number | null) =>
|
|
108
|
+
const percentChange = (price: number, reference: number | null | undefined) =>
|
|
104
109
|
reference != null && reference > 0 ? (price / reference - 1) * 100 : null;
|
|
105
110
|
|
|
111
|
+
/** What the board's day change is measured from, as `quote` labels a coin's: its header says it (CHG% UTC). */
|
|
112
|
+
export const CRYPTO_CHANGE_BASIS_LABEL = "since 00:00 UTC";
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A coin's move since 00:00 UTC, the basis `quote` gives a coin: its live
|
|
116
|
+
* quote's while that quote is measured from the previous close, otherwise the
|
|
117
|
+
* board's, which Gloom Cloud measures the same way (with a live price, from
|
|
118
|
+
* the board's previous close). A coin either says moves over 24 hours, or not
|
|
119
|
+
* at all, has none here, so the column never mixes the two.
|
|
120
|
+
*/
|
|
121
|
+
function utcDayChangePercent(asset: CryptoMarketAsset, quote: Quote | null, price: number): number | null {
|
|
122
|
+
if (quote && quote.changeBasis == null) {
|
|
123
|
+
if (isFiniteNumber(quote.changePercent)) return quote.changePercent;
|
|
124
|
+
if (positive(quote.previousClose)) return percentChange(price, quote.previousClose);
|
|
125
|
+
}
|
|
126
|
+
if (asset.changeBasis != null) return null;
|
|
127
|
+
return quote ? percentChange(price, asset.previousClose) : asset.changePercent;
|
|
128
|
+
}
|
|
129
|
+
|
|
106
130
|
/** A live quote replaces the board's snapshot once it is at least as new. */
|
|
107
131
|
function liveQuote(
|
|
108
132
|
asset: CryptoMarketAsset,
|
|
@@ -134,11 +158,7 @@ function buildCryptoRow(
|
|
|
134
158
|
now = Date.now(),
|
|
135
159
|
): CryptoRow {
|
|
136
160
|
const price = quote?.price ?? asset.price;
|
|
137
|
-
const changePercent = quote
|
|
138
|
-
? quote.changePercent
|
|
139
|
-
: quote
|
|
140
|
-
? percentChange(price, asset.previousClose)
|
|
141
|
-
: asset.changePercent;
|
|
161
|
+
const changePercent = utcDayChangePercent(asset, quote, price);
|
|
142
162
|
// Supply moves slowly; the price is what makes market cap live. Scaling the
|
|
143
163
|
// reported cap keeps the source's supply basis and the board's ranking.
|
|
144
164
|
const marketCap = isFiniteNumber(asset.marketCap)
|
|
@@ -389,7 +409,7 @@ const COLUMN_SPECS: CryptoColumnSpec[] = [
|
|
|
389
409
|
{ id: "code", label: "COIN", width: 7, align: "left" },
|
|
390
410
|
{ id: "name", label: "NAME", width: 10, align: "left", optional: 1 },
|
|
391
411
|
{ id: "price", label: "PRICE", width: 13, align: "right" },
|
|
392
|
-
{ id: "changePercent", label: "CHG%", width: 8, align: "right" },
|
|
412
|
+
{ id: "changePercent", label: "CHG% UTC", title: "Change since 00:00 UTC", width: 8, align: "right" },
|
|
393
413
|
{ id: "return7d", label: "7D%", width: 8, align: "right" },
|
|
394
414
|
{ id: "return30d", label: "30D%", width: 8, align: "right", optional: 3 },
|
|
395
415
|
{ id: "return1y", label: "1Y%", width: 10, align: "right", optional: 5 },
|
|
@@ -415,7 +435,7 @@ const STABLECOIN_COLUMN_SPECS: CryptoColumnSpec[] = [
|
|
|
415
435
|
{ id: "low24hAt", label: "LOW UTC", width: 9, align: "left" },
|
|
416
436
|
{ id: "high24h", label: "24H HIGH", width: 8, align: "right", optional: 1 },
|
|
417
437
|
{ id: "high24hAt", label: "HIGH UTC", width: 9, align: "left", optional: 2 },
|
|
418
|
-
{ id: "changePercent", label: "CHG%", width: 8, align: "right", optional: 5 },
|
|
438
|
+
{ id: "changePercent", label: "CHG% UTC", title: "Change since 00:00 UTC", width: 8, align: "right", optional: 5 },
|
|
419
439
|
{ id: "return7d", label: "7D%", width: 8, align: "right", optional: 6 },
|
|
420
440
|
{ id: "return30d", label: "30D%", width: 8, align: "right", optional: 8 },
|
|
421
441
|
{ id: "return1y", label: "1Y%", width: 10, align: "right", optional: 9 },
|
|
@@ -410,8 +410,9 @@ export function DebtMaturitiesPane({ width, height, focused }: PaneProps) {
|
|
|
410
410
|
const detailOpen =
|
|
411
411
|
tab === "maturities" ? !!openBucketRow : tab === "history" && !!openHistoryRow;
|
|
412
412
|
const tabsFocused = focused && !detailOpen;
|
|
413
|
+
const signInWall = !data && isCloudSessionRequired(resource.error);
|
|
413
414
|
const { strip: tabStrip, rows: tabRows } = usePaneTabs(
|
|
414
|
-
|
|
415
|
+
symbol && !signInWall
|
|
415
416
|
? { tabs: TABS, activeValue: tab, onSelect: setTab, focused: tabsFocused, dense: true }
|
|
416
417
|
: null,
|
|
417
418
|
);
|
|
@@ -522,7 +523,7 @@ export function DebtMaturitiesPane({ width, height, focused }: PaneProps) {
|
|
|
522
523
|
message="Select a ticker to view debt maturities."
|
|
523
524
|
/>
|
|
524
525
|
);
|
|
525
|
-
if (
|
|
526
|
+
if (signInWall)
|
|
526
527
|
return (
|
|
527
528
|
<SignInWall
|
|
528
529
|
placement="debt-maturities-signin"
|
|
@@ -532,6 +533,7 @@ export function DebtMaturitiesPane({ width, height, focused }: PaneProps) {
|
|
|
532
533
|
);
|
|
533
534
|
return (
|
|
534
535
|
<Box width={width} height={height} flexDirection="column">
|
|
536
|
+
{tabStrip}
|
|
535
537
|
<PaneStatusBody
|
|
536
538
|
loading={resource.loading && !data}
|
|
537
539
|
error={!data ? resource.error : null}
|
|
@@ -542,7 +544,6 @@ export function DebtMaturitiesPane({ width, height, focused }: PaneProps) {
|
|
|
542
544
|
>
|
|
543
545
|
{latest ? (
|
|
544
546
|
<>
|
|
545
|
-
{tabStrip}
|
|
546
547
|
{tab === "filing" ? (
|
|
547
548
|
<FilingDetail latest={latest} quarter={quarter} width={width} height={bodyHeight} />
|
|
548
549
|
) : tab === "maturities" ? (
|