gloomberb 0.16.4 → 0.16.5
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/README.md +14 -91
- package/package.json +4 -1
- package/src/{plugins/builtin/cloud → account}/auth-form.tsx +9 -9
- package/src/{plugins/builtin/cloud → account}/auth-model.ts +4 -4
- package/src/account/billing.ts +57 -0
- package/src/{plugins/builtin/cloud → account}/device-signin-dialog.tsx +18 -12
- package/src/{plugins/builtin/cloud → account}/device-signin.ts +2 -2
- package/src/{plugins/builtin/cloud → account}/email-auth-attempt.ts +2 -2
- package/src/account/session.ts +16 -0
- package/src/{plugins/builtin/cloud → account}/upgrade-dialog.tsx +16 -15
- package/src/{plugins/builtin/cloud → account}/upgrade-personalization.ts +7 -7
- package/src/{plugins/builtin/cloud → account}/upgrade-tickers.ts +6 -6
- package/src/{plugins/builtin/cloud → account}/verification-panel.tsx +6 -6
- package/src/api-client/data.ts +24 -2
- package/src/api-client/earnings.ts +6 -0
- package/src/api-client/futures-curve.ts +56 -0
- package/src/api-client/index.ts +4 -1
- package/src/api-client/market-discovery.ts +10 -1
- package/src/api-client/paths.ts +7 -0
- package/src/api-client/research-activity.ts +15 -10
- package/src/api-client/types.ts +17 -1
- package/src/api-client/yield-curves.ts +8 -0
- package/src/app/global-shortcuts.ts +15 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +7 -3
- package/src/architecture/core-builtin-allowlist.json +4 -13
- package/src/brokers/signed-in/sign-in-dialog.tsx +11 -5
- package/src/capabilities/chart-series.ts +11 -1
- package/src/cli/commands/crypto-hints.ts +7 -0
- package/src/cli/commands/fred-series.ts +61 -0
- package/src/cli/commands/market.ts +407 -104
- package/src/cli/commands/news-hints.ts +62 -0
- package/src/cli/commands/news-search.ts +53 -0
- package/src/cli/commands/options-leaps.ts +5 -2
- package/src/cli/commands/overview.ts +93 -20
- package/src/cli/commands/remote.ts +2 -1
- package/src/cli/commands/search.ts +82 -9
- package/src/cli/commands/system.ts +55 -8
- package/src/cli/commands/ticker.ts +88 -11
- package/src/cli/context.ts +45 -14
- package/src/cli/desktop-pane-shot.ts +59 -7
- package/src/cli/earnings-calendar.ts +71 -0
- package/src/cli/freshness.ts +18 -7
- package/src/cli/glossary.ts +429 -0
- package/src/cli/history-rows.ts +48 -7
- package/src/cli/index-roots.ts +7 -0
- package/src/cli/index.ts +21 -7
- package/src/cli/known-gaps.ts +27 -0
- package/src/cli/listing-arg.ts +417 -56
- package/src/cli/not-a-ticker.ts +12 -5
- package/src/cli/pane-functions/access-gate.ts +5 -0
- package/src/cli/pane-functions/capabilities.ts +3 -2
- package/src/cli/pane-functions/catalog.ts +35 -5
- package/src/cli/pane-functions/data.ts +12 -8
- package/src/cli/pane-functions/discovery.ts +1 -0
- package/src/cli/pane-functions/dom.ts +20 -6
- package/src/cli/pane-functions/freshness.ts +363 -43
- package/src/cli/pane-functions/function-help.ts +12 -5
- package/src/cli/pane-functions/headless.ts +45 -17
- package/src/cli/pane-functions/index.ts +63 -10
- package/src/cli/pane-functions/market-session.ts +22 -8
- package/src/cli/pane-functions/options.ts +63 -22
- package/src/cli/pane-functions/report-notices.ts +25 -7
- package/src/cli/pane-functions/report.ts +15 -8
- package/src/cli/pane-functions/resolver.ts +49 -12
- package/src/cli/pane-functions/screenshot.ts +110 -23
- package/src/cli/report-tables.ts +3 -0
- package/src/cli/result.ts +4 -1
- package/src/cli/staged-tickers.ts +52 -0
- package/src/cli/symbol-args.ts +9 -0
- package/src/cli/symbol-list.ts +172 -0
- package/src/cli/ticker-resolution.ts +223 -30
- package/src/cli/types.ts +5 -0
- package/src/components/chart/composite/axis-overlays.tsx +6 -2
- package/src/components/chart/composite/composite-chart.tsx +40 -6
- package/src/components/chart/composite/legend.tsx +49 -2
- package/src/components/chart/composite/panel-surface.tsx +44 -17
- package/src/components/chart/composite/price-axis-labels.tsx +3 -1
- package/src/components/command-bar/panel/index.tsx +11 -3
- package/src/components/command-bar/panel/runtime.ts +3 -0
- package/src/components/command-bar/panel/types.ts +2 -0
- package/src/components/command-bar/routes/root/provider-search.ts +4 -3
- package/src/components/command-bar/routes/ticker-search/results.ts +12 -11
- package/src/components/command-bar/routes/ticker-search/route.ts +4 -3
- package/src/components/command-bar/workflow/tickers.ts +23 -4
- package/src/components/layout/detached-pane-shell.tsx +9 -0
- package/src/components/layout/floating-pane.tsx +3 -0
- package/src/components/layout/header.tsx +20 -5
- package/src/components/layout/pane/content.tsx +7 -13
- package/src/components/layout/pane/footer/index.tsx +6 -2
- package/src/components/layout/pane/footer/model.ts +35 -1
- package/src/components/layout/pane/header.tsx +24 -1
- package/src/components/layout/pane/index.tsx +12 -2
- package/src/components/layout/pane/sidebar.tsx +78 -10
- package/src/components/layout/shell/active-drag.ts +21 -5
- package/src/components/layout/shell/drag/index.ts +39 -9
- package/src/components/layout/shell/drag/live.ts +61 -51
- package/src/components/layout/shell/drag/runtime.ts +54 -12
- package/src/components/layout/shell/index.tsx +25 -7
- package/src/components/layout/shell/menu.ts +5 -0
- package/src/components/layout/shell/native/pointer-runtime.ts +3 -1
- package/src/components/layout/shell/pane/layers.tsx +192 -45
- package/src/components/layout/shell/terminal-pointer-runtime.ts +24 -0
- package/src/components/markdown-text.tsx +62 -20
- package/src/components/metric-treemap/heat-surface.tsx +10 -1
- package/src/components/onboarding/account-step/form-panel.tsx +1 -1
- package/src/components/onboarding/account-step/index.tsx +1 -1
- package/src/components/onboarding/account-step/qr-panel.tsx +1 -1
- package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
- package/src/components/onboarding/onboarding-keyboard.ts +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +2 -2
- package/src/components/onboarding/use-onboarding-upgrade.ts +3 -3
- package/src/components/onboarding/wizard-account.ts +2 -2
- package/src/components/pane-template-wizard.tsx +9 -1
- package/src/components/price-selector-dialog.tsx +11 -2
- package/src/components/sign-in-gate.tsx +3 -3
- package/src/components/ticker/list-table-view.tsx +24 -1
- package/src/components/ui/action-row.tsx +2 -1
- package/src/components/ui/button.tsx +7 -1
- package/src/components/ui/choice-dialog.tsx +6 -4
- package/src/components/ui/confirm-dialog.tsx +28 -18
- package/src/components/ui/data-table/index.tsx +17 -6
- package/src/components/ui/data-table/opentui/index.tsx +37 -6
- package/src/components/ui/data-table/types.ts +12 -0
- package/src/components/ui/dialog-actions.tsx +64 -0
- package/src/components/ui/display.tsx +17 -3
- package/src/components/ui/frame.tsx +35 -2
- package/src/components/ui/icon.tsx +2 -0
- package/src/components/ui/text-prompt-dialog.tsx +11 -9
- package/src/data/cached-query.ts +2 -0
- package/src/data/config/store/normalize.ts +7 -2
- package/src/data/ticker-repository.ts +5 -0
- package/src/data/ticker-store.ts +15 -0
- package/src/docs/content.ts +215 -0
- package/src/docs/load.ts +139 -0
- package/src/docs/requests.ts +39 -0
- package/src/docs/snapshot.chunk.ts +8 -0
- package/src/docs/snapshot.json +7416 -0
- package/src/docs/topics.ts +216 -0
- package/src/i18n/es.ts +61 -4
- package/src/i18n/ja.ts +61 -4
- package/src/i18n/ko.ts +61 -4
- package/src/i18n/zh-cn.ts +61 -4
- package/src/i18n/zh-tw.ts +61 -4
- package/src/layout/desks.ts +1 -1
- package/src/layout/pane-manager/dock-tree.ts +18 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/chart.ts +10 -4
- package/src/market-data/coordinator/index.ts +6 -3
- package/src/{plugins/builtin/futures/contracts.ts → market-data/futures-board.ts} +81 -28
- package/src/market-data/futures-contract.ts +151 -0
- package/src/market-data/hooks.tsx +10 -2
- package/src/market-data/market/format.ts +39 -21
- package/src/market-data/market/freshness.ts +81 -18
- package/src/market-data/market/status.ts +19 -23
- package/src/market-data/published-apac-sessions.ts +23 -1
- package/src/market-data/quotes/contributions.ts +3 -0
- package/src/market-data/quotes/metadata.ts +7 -24
- package/src/market-data/quotes/resolution.ts +2 -0
- package/src/market-data/quotes/screener-live-quotes.ts +5 -4
- package/src/market-data/result-types.ts +2 -0
- package/src/plugins/builtin/account-management/model.ts +1 -56
- package/src/plugins/builtin/account-management/pane.tsx +6 -4
- package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
- package/src/plugins/builtin/alerts/cli.ts +313 -47
- package/src/plugins/builtin/alerts/events-pane.tsx +45 -7
- package/src/plugins/builtin/alerts/events.ts +23 -2
- package/src/plugins/builtin/alerts/history-pane.tsx +7 -2
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +41 -9
- package/src/plugins/builtin/alerts/pane.tsx +8 -0
- package/src/plugins/builtin/alerts/research-builder.ts +8 -3
- package/src/plugins/builtin/alerts/research-rules.ts +18 -2
- package/src/plugins/builtin/alerts/scheduled-check.ts +122 -0
- package/src/plugins/builtin/alerts/scheduled-sources.ts +25 -0
- package/src/plugins/builtin/alerts/scheduled.ts +254 -0
- package/src/plugins/builtin/alerts/targets.ts +92 -0
- package/src/plugins/builtin/analytics/index.tsx +4 -2
- package/src/plugins/builtin/analytics/risk-headless.ts +30 -12
- package/src/plugins/builtin/analytics/risk-math.ts +91 -27
- package/src/plugins/builtin/analytics/risk-model.ts +35 -5
- package/src/plugins/builtin/analytics/risk-pane.tsx +15 -8
- package/src/plugins/builtin/attention/index.tsx +2 -0
- package/src/plugins/builtin/attention/pane.tsx +3 -1
- package/src/plugins/builtin/attention/series.ts +26 -0
- package/src/plugins/builtin/awards/index.tsx +2 -0
- package/src/plugins/builtin/awards/pane.tsx +3 -1
- package/src/plugins/builtin/awards/series.ts +29 -0
- package/src/plugins/builtin/bond-calculator/model.ts +2 -1
- package/src/plugins/builtin/catalysts/pane.tsx +3 -1
- package/src/plugins/builtin/cds/client.ts +70 -13
- package/src/plugins/builtin/cds/headless.ts +116 -19
- package/src/plugins/builtin/cds/index.tsx +3 -1
- package/src/plugins/builtin/cds/model.ts +193 -31
- package/src/plugins/builtin/cds/pane.tsx +56 -14
- package/src/plugins/builtin/cds/series.ts +19 -0
- package/src/plugins/builtin/central-bank-rates/index.tsx +2 -0
- package/src/plugins/builtin/central-bank-rates/series.ts +42 -0
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +192 -255
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +84 -46
- package/src/plugins/builtin/chart-composer/editor.tsx +5 -4
- package/src/plugins/builtin/chart-composer/field-series.ts +40 -0
- package/src/plugins/builtin/chart-composer/headless.ts +8 -3
- package/src/plugins/builtin/chart-composer/index.tsx +9 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +58 -50
- package/src/plugins/builtin/chart-composer/series-expression.ts +18 -10
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +59 -3
- package/src/plugins/builtin/chart-composer/user-series.ts +84 -0
- package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +10 -2
- package/src/plugins/builtin/chat/controller/index.ts +2 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +111 -19
- package/src/plugins/builtin/cloud/askg/executor.ts +57 -3
- package/src/plugins/builtin/cloud/askg/manifest.ts +32 -7
- package/src/plugins/builtin/cloud/askg/model.ts +27 -8
- package/src/plugins/builtin/cloud/askg/protocol.ts +10 -1
- package/src/plugins/builtin/cloud/askg/tool-display.ts +3 -0
- package/src/plugins/builtin/cloud/askg/undo.ts +2 -0
- package/src/plugins/builtin/cloud/auth-actions.tsx +1 -1
- package/src/plugins/builtin/cloud/auth-commands.ts +1 -1
- package/src/plugins/builtin/cloud/auth-dialog.tsx +3 -3
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +3 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +77 -9
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +6 -1
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +9 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +35 -11
- package/src/plugins/builtin/cloud/thesis/exposure.ts +1 -1
- package/src/plugins/builtin/cloud/thesis/prompts.tsx +2 -1
- package/src/plugins/builtin/company-attention/app-rank.tsx +1 -1
- package/src/plugins/builtin/company-attention/headless.ts +19 -5
- package/src/plugins/builtin/company-attention/index.tsx +2 -0
- package/src/plugins/builtin/company-attention/model.ts +26 -7
- package/src/plugins/builtin/company-attention/pane.tsx +12 -9
- package/src/plugins/builtin/company-attention/series.ts +52 -0
- package/src/plugins/builtin/company-kpis/pane.tsx +4 -2
- package/src/plugins/builtin/composite-plugins.ts +19 -13
- package/src/plugins/builtin/cot/index.tsx +3 -1
- package/src/plugins/builtin/cot/model.ts +40 -11
- package/src/plugins/builtin/cot/series.ts +27 -0
- package/src/plugins/builtin/credit-boards/index.tsx +2 -0
- package/src/plugins/builtin/credit-boards/series.ts +66 -0
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -0
- package/src/plugins/builtin/credit-conditions/series.ts +26 -0
- package/src/plugins/builtin/credit-documents/headless.ts +31 -1
- package/src/plugins/builtin/credit-documents/pane.tsx +3 -1
- package/src/plugins/builtin/crypto-board/headless.ts +2 -3
- package/src/plugins/builtin/crypto-board/index.tsx +2 -0
- package/src/plugins/builtin/crypto-board/model.ts +12 -0
- package/src/plugins/builtin/crypto-board/pane.tsx +19 -12
- package/src/plugins/builtin/crypto-board/series.ts +48 -0
- package/src/plugins/builtin/custom-view/live-quotes.ts +2 -2
- package/src/plugins/builtin/debt-maturities/index.tsx +2 -0
- package/src/plugins/builtin/debt-maturities/series.ts +45 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +2 -2
- package/src/plugins/builtin/dividend-yield/index.tsx +2 -0
- package/src/plugins/builtin/dividend-yield/series.ts +20 -0
- package/src/plugins/builtin/docs/blocks.tsx +181 -0
- package/src/plugins/builtin/docs/index.tsx +68 -0
- package/src/plugins/builtin/docs/pane.tsx +426 -0
- package/src/plugins/builtin/docs/targets.ts +72 -0
- package/src/plugins/builtin/docs/use-docs.ts +60 -0
- package/src/plugins/builtin/doe/index.tsx +2 -0
- package/src/plugins/builtin/doe/series.ts +62 -0
- package/src/plugins/builtin/earnings/client.ts +3 -2
- package/src/plugins/builtin/earnings/format.ts +1 -5
- package/src/plugins/builtin/earnings/headless.ts +58 -6
- package/src/plugins/builtin/earnings/history-model.ts +15 -1
- package/src/plugins/builtin/earnings/history-table.ts +13 -2
- package/src/plugins/builtin/earnings/history.tsx +7 -3
- package/src/plugins/builtin/econ/fred-catalog.ts +189 -0
- package/src/plugins/builtin/econ/fred-series-map.ts +0 -46
- package/src/plugins/builtin/econ/index.tsx +2 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +5 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +19 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +1 -0
- package/src/plugins/builtin/equity-screener/pane.tsx +4 -11
- package/src/plugins/builtin/exposure/holdings.ts +1 -1
- package/src/plugins/builtin/exposure/pane.tsx +3 -1
- package/src/plugins/builtin/fear-greed/index.tsx +4 -0
- package/src/plugins/builtin/fear-greed/series.ts +52 -0
- package/src/plugins/builtin/futures/index.tsx +43 -3
- package/src/plugins/builtin/futures/model.ts +1 -1
- package/src/plugins/builtin/futures/returns.ts +1 -1
- package/src/plugins/builtin/futures/series.ts +13 -0
- package/src/plugins/builtin/futures/table.tsx +1 -1
- package/src/plugins/builtin/futures/use-front-returns.ts +1 -1
- package/src/plugins/builtin/futures-curve/client.ts +12 -3
- package/src/plugins/builtin/futures-curve/headless.ts +70 -9
- package/src/plugins/builtin/futures-curve/index.tsx +1 -1
- package/src/plugins/builtin/futures-curve/model.ts +66 -11
- package/src/plugins/builtin/futures-curve/pane.tsx +52 -18
- package/src/plugins/builtin/fx-matrix/index.tsx +1 -1
- package/src/plugins/builtin/gpu/board.tsx +13 -10
- package/src/plugins/builtin/gpu/changes.tsx +4 -4
- package/src/plugins/builtin/gpu/client.ts +19 -1
- package/src/plugins/builtin/gpu/equities.tsx +14 -10
- package/src/plugins/builtin/gpu/headless.ts +56 -25
- package/src/plugins/builtin/gpu/history.tsx +3 -3
- package/src/plugins/builtin/gpu/index.tsx +2 -0
- package/src/plugins/builtin/gpu/model.ts +101 -16
- package/src/plugins/builtin/gpu/pane.tsx +28 -12
- package/src/plugins/builtin/gpu/series.ts +38 -0
- package/src/plugins/builtin/help/function-card.tsx +25 -6
- package/src/plugins/builtin/help/index.tsx +17 -8
- package/src/plugins/builtin/holders/beneficial-model.ts +90 -3
- package/src/plugins/builtin/holders/beneficial-report.ts +57 -5
- package/src/plugins/builtin/holders/beneficial-view.tsx +55 -11
- package/src/plugins/builtin/holders/client.ts +22 -3
- package/src/plugins/builtin/holders/format.ts +18 -3
- package/src/plugins/builtin/holders/headless.ts +36 -133
- package/src/plugins/builtin/holders/pane.tsx +11 -4
- package/src/plugins/builtin/holders/report-header.ts +40 -5
- package/src/plugins/builtin/holders/report-rows.ts +240 -0
- package/src/plugins/builtin/insider/client.ts +35 -20
- package/src/plugins/builtin/insider/headless.ts +41 -11
- package/src/plugins/builtin/insider/index.tsx +26 -9
- package/src/plugins/builtin/insider/insider-data.ts +10 -0
- package/src/plugins/builtin/insider/issuer.ts +49 -0
- package/src/plugins/builtin/insider/model.ts +3 -0
- package/src/plugins/builtin/iv-history/client.ts +14 -4
- package/src/plugins/builtin/iv-history/headless.ts +7 -4
- package/src/plugins/builtin/iv-history/index.tsx +2 -0
- package/src/plugins/builtin/iv-history/model.ts +111 -7
- package/src/plugins/builtin/iv-history/screen-pane.tsx +5 -9
- package/src/plugins/builtin/iv-history/series.ts +29 -0
- package/src/plugins/builtin/jobs/headless.ts +28 -3
- package/src/plugins/builtin/jobs/index.tsx +3 -1
- package/src/plugins/builtin/jobs/series.ts +19 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +2 -1
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +0 -39
- package/src/plugins/builtin/macro-day/client.ts +44 -3
- package/src/plugins/builtin/macro-day/headless.ts +13 -10
- package/src/plugins/builtin/macro-day/model.ts +56 -10
- package/src/plugins/builtin/macro-day/pane.tsx +30 -20
- package/src/plugins/builtin/market-heatmap/model.ts +19 -11
- package/src/plugins/builtin/market-heatmap/portfolio.ts +2 -2
- package/src/plugins/builtin/market-movers/client.ts +7 -5
- package/src/plugins/builtin/market-movers/index.tsx +3 -2
- package/src/plugins/builtin/market-movers/model.ts +30 -0
- package/src/plugins/builtin/market-movers/screener.ts +31 -0
- package/src/plugins/builtin/market-valuation/chart-series.ts +29 -69
- package/src/plugins/builtin/market-valuation/index.tsx +3 -3
- package/src/plugins/builtin/market-valuation/indicators.ts +0 -5
- package/src/plugins/builtin/members/headless.ts +7 -2
- package/src/plugins/builtin/members/model.ts +31 -3
- package/src/plugins/builtin/members/pane.tsx +3 -6
- package/src/plugins/builtin/mna/headless.ts +15 -3
- package/src/plugins/builtin/money-markets/index.tsx +2 -0
- package/src/plugins/builtin/money-markets/series.ts +41 -0
- package/src/plugins/builtin/notes/cli.ts +12 -7
- package/src/plugins/builtin/options/view.tsx +1 -1
- package/src/plugins/builtin/options-calculator/inputs.ts +2 -1
- package/src/plugins/builtin/options-positioning/client.ts +7 -3
- package/src/plugins/builtin/options-positioning/headless.ts +1 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +2 -1
- package/src/plugins/builtin/options-scenario/headless.ts +1 -0
- package/src/plugins/builtin/options-scenario/hedge.ts +2 -1
- package/src/plugins/builtin/perps/client.ts +40 -6
- package/src/plugins/builtin/perps/equity-row.tsx +33 -11
- package/src/plugins/builtin/perps/freshness.ts +66 -0
- package/src/plugins/builtin/perps/headless.ts +45 -30
- package/src/plugins/builtin/perps/index.tsx +18 -1
- package/src/plugins/builtin/perps/model.ts +20 -5
- package/src/plugins/builtin/perps/pane.tsx +76 -20
- package/src/plugins/builtin/perps/series.ts +76 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +67 -61
- package/src/plugins/builtin/plugin-module.ts +3 -0
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +1 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +273 -0
- package/src/plugins/builtin/portfolio-list/cli/import-13f.ts +109 -0
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +331 -65
- package/src/plugins/builtin/portfolio-list/cli/render.ts +200 -31
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +24 -0
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -47
- package/src/plugins/builtin/portfolio-list/column-values.ts +1 -1
- package/src/plugins/builtin/portfolio-list/grid.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/headless.ts +34 -4
- package/src/plugins/builtin/portfolio-list/imported-from.ts +8 -0
- package/src/plugins/builtin/portfolio-list/live-valuation.ts +6 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +17 -1
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +7 -1
- package/src/plugins/builtin/power/client.ts +16 -0
- package/src/plugins/builtin/power/evidence.ts +6 -2
- package/src/plugins/builtin/power/headless.ts +71 -7
- package/src/plugins/builtin/power/index.tsx +4 -1
- package/src/plugins/builtin/power/model.ts +72 -1
- package/src/plugins/builtin/power/pane.tsx +32 -9
- package/src/plugins/builtin/rate-path/headless.ts +11 -3
- package/src/plugins/builtin/realized-vol/headless.ts +5 -0
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -2
- package/src/plugins/builtin/research/event-model.ts +68 -24
- package/src/plugins/builtin/research/events-headless.ts +2 -1
- package/src/plugins/builtin/research/headless.ts +2 -1
- package/src/plugins/builtin/seasonality/client.ts +15 -7
- package/src/plugins/builtin/seasonality/headless.ts +36 -12
- package/src/plugins/builtin/seasonality/index.tsx +2 -2
- package/src/plugins/builtin/seasonality/model.ts +119 -0
- package/src/plugins/builtin/seasonality/pane.tsx +108 -37
- package/src/plugins/builtin/series-inventory.ts +91 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -1
- package/src/plugins/builtin/shared/command-tab.ts +30 -12
- package/src/plugins/builtin/shared/earnings-report.ts +55 -0
- package/src/plugins/builtin/shared/fred-points.ts +20 -0
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +101 -0
- package/src/plugins/builtin/shared/open-description.ts +29 -0
- package/src/plugins/builtin/shared/report-freshness.ts +9 -0
- package/src/plugins/builtin/shared/series-warning.ts +5 -0
- package/src/plugins/builtin/short-interest/headless.ts +1 -0
- package/src/plugins/builtin/short-interest/index.tsx +2 -0
- package/src/plugins/builtin/short-interest/series.ts +36 -0
- package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
- package/src/plugins/builtin/short-interest/watch-model.ts +9 -5
- package/src/plugins/builtin/short-volume/headless.ts +1 -0
- package/src/plugins/builtin/short-volume/index.tsx +2 -0
- package/src/plugins/builtin/short-volume/series.ts +23 -0
- package/src/plugins/builtin/social-mentions/index.tsx +2 -0
- package/src/plugins/builtin/social-mentions/series.ts +23 -0
- package/src/plugins/builtin/supply-chain/graph-pane.tsx +3 -2
- package/src/plugins/builtin/supply-chain/pane.tsx +3 -1
- package/src/plugins/builtin/thirteenf/api.ts +1 -0
- package/src/plugins/builtin/thirteenf/data.ts +6 -4
- package/src/plugins/builtin/thirteenf/headless.ts +217 -36
- package/src/plugins/builtin/thirteenf/model.ts +39 -1
- package/src/plugins/builtin/thirteenf/overlap.ts +75 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +10 -1
- package/src/plugins/builtin/thirteenf/report-notes.ts +103 -1
- package/src/plugins/builtin/thirteenf/types.ts +2 -0
- package/src/plugins/builtin/ticker-detail/index.tsx +3 -0
- package/src/plugins/builtin/ticker-detail/overview/components.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/overview/futures-contract.ts +31 -0
- package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -3
- package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +48 -4
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +13 -3
- package/src/plugins/builtin/time-sales/headless.ts +1 -0
- package/src/plugins/builtin/treasury-auctions/headless.ts +16 -2
- package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
- package/src/plugins/builtin/treasury-auctions/model.ts +8 -0
- package/src/plugins/builtin/vol-surface/headless.ts +1 -0
- package/src/plugins/builtin/volatility/index.tsx +2 -0
- package/src/plugins/builtin/volatility/series.ts +84 -0
- package/src/plugins/builtin/world-venue-map/geo-series.ts +26 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +4 -1
- package/src/plugins/builtin/world-venue-map/index.ts +2 -1
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +8 -4
- package/src/plugins/builtin/world-venue-map/layers.ts +29 -0
- package/src/plugins/builtin/yield-curve/chart.ts +2 -2
- package/src/plugins/builtin/yield-curve/curves.ts +21 -8
- package/src/plugins/builtin/yield-curve/headless.ts +71 -18
- package/src/plugins/builtin/yield-curve/index.tsx +3 -1
- package/src/plugins/builtin/yield-curve/series.ts +86 -0
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/registry/index.ts +89 -4
- package/src/{plugins/builtin/portfolio-list → portfolio}/position-metrics.ts +6 -6
- package/src/portfolio/position-value.ts +40 -0
- package/src/public/__snapshots__/public-api.test.ts.snap +7 -0
- package/src/public/time-series.ts +17 -0
- package/src/react/auto-refresh.ts +13 -3
- package/src/remote/app-host.tsx +20 -4
- package/src/remote/confirm-change.tsx +85 -0
- package/src/remote/controller-utils.ts +8 -1
- package/src/remote/controller.ts +123 -6
- package/src/remote/pane-screenshot.ts +47 -0
- package/src/remote/schema.ts +62 -0
- package/src/remote/types.ts +30 -0
- package/src/remote/watchlist.ts +306 -0
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/worker.ts +1 -1
- package/src/renderers/dom/data-table/frozen-column.ts +3 -1
- package/src/renderers/dom/data-table/index.tsx +33 -6
- package/src/renderers/dom/data-table/row.tsx +27 -1
- package/src/renderers/dom/desktop/control-styles.ts +63 -1
- package/src/renderers/dom/desktop/controls.tsx +30 -23
- package/src/renderers/dom/desktop/dialog-actions.tsx +170 -0
- package/src/renderers/dom/desktop/icons.tsx +7 -0
- package/src/renderers/dom/dom-ui-host.tsx +3 -0
- package/src/renderers/dom/host/box.tsx +3 -0
- package/src/renderers/dom/host/chart-surface.tsx +6 -2
- package/src/renderers/dom/host/live-frame.ts +6 -2
- package/src/renderers/dom/host/overflow-fade.ts +14 -1
- package/src/renderers/dom/host/size-observer.ts +26 -0
- package/src/renderers/dom/host/surface-3d.tsx +7 -2
- package/src/renderers/dom/host/text.tsx +1 -1
- package/src/renderers/dom/styles.css +61 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +17 -2
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +19 -11
- package/src/renderers/electrobun/view/cli-pane-shot-health.ts +10 -26
- package/src/renderers/electrobun/view/main.tsx +2 -0
- package/src/series/capability.ts +75 -0
- package/src/series/csv.ts +160 -0
- package/src/series/files.ts +107 -0
- package/src/series/registry.ts +198 -0
- package/src/series/resolve.ts +107 -0
- package/src/series/standalone.ts +48 -0
- package/src/series/types.ts +145 -0
- package/src/series/validate.ts +171 -0
- package/src/sources/gloomberb-cloud/index.ts +7 -7
- package/src/sources/gloomberb-cloud/normalizers.ts +1 -0
- package/src/sources/history-result.ts +1 -0
- package/src/sources/listing-symbols.ts +4 -1
- package/src/sources/provider-router/batches.ts +4 -1
- package/src/sources/provider-router/cache.ts +4 -2
- package/src/sources/provider-router/cached-routes.ts +1 -1
- package/src/sources/provider-router/financial-routes.ts +1 -0
- package/src/sources/provider-router/financials.ts +13 -0
- package/src/sources/provider-router/history.ts +43 -25
- package/src/sources/provider-router/search.ts +1 -0
- package/src/sync/core-contributors.ts +1 -0
- package/src/tickers/company-name.ts +75 -0
- package/src/tickers/instrument-kind.ts +25 -0
- package/src/tickers/open-target.ts +3 -2
- package/src/tickers/search/index.ts +45 -7
- package/src/tickers/search/ranking.ts +33 -17
- package/src/tickers/search/types.ts +7 -1
- package/src/tickers/us-listing.ts +34 -0
- package/src/time-series/hooks.ts +9 -0
- package/src/time-series/resolve.ts +95 -16
- package/src/time-series/types.ts +4 -2
- package/src/time-series/use-chart-resolution.ts +32 -0
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +5 -0
- package/src/types/financials.ts +52 -1
- package/src/types/headless.ts +61 -0
- package/src/types/plugin.ts +19 -0
- package/src/types/price-history.ts +7 -0
- package/src/types/ticker.ts +14 -0
- package/src/ui/host.tsx +18 -0
- package/src/ui/live-resize.ts +81 -0
- package/src/ui/live-stretch.ts +121 -0
- package/src/ui/measured-cells.ts +58 -0
- package/src/ui/pane-fill.ts +11 -0
- package/src/utils/basis-points.ts +17 -0
- package/src/utils/cli-output.ts +7 -1
- package/src/utils/concurrency.ts +25 -0
- package/src/utils/crypto-pair.ts +20 -0
- package/src/utils/currency-pair.ts +19 -0
- package/src/utils/decimal-comma.ts +19 -0
- package/src/utils/dom-screenshot-css.ts +265 -0
- package/src/utils/dom-screenshot-rasters.ts +276 -0
- package/src/utils/dom-screenshot.ts +437 -52
- package/src/utils/exchange-rate-snapshot.ts +6 -2
- package/src/utils/exchanges.ts +107 -1
- package/src/utils/format.ts +16 -3
- package/src/utils/fundamentals.ts +66 -0
- package/src/utils/futures-generic.ts +6 -12
- package/src/utils/fx-market-hours.ts +15 -5
- package/src/utils/price-history.ts +99 -42
- package/src/utils/sec-eps-basis.ts +24 -11
- package/src/utils/sec.ts +61 -7
- package/src/utils/utc-time.ts +51 -4
- package/src/plugins/builtin/fx-matrix/freshness.ts +0 -65
- /package/src/{plugins/builtin/cloud → tickers}/starter-symbols.ts +0 -0
|
@@ -3,7 +3,7 @@ import type { CurvePalette, CurveSeries } from "../../../components/chart/curve/
|
|
|
3
3
|
import { spanDigits } from "../../../components/chart-table";
|
|
4
4
|
import { compositeAxisTicks } from "../../../components/chart/composite/format";
|
|
5
5
|
import type { CompositeAxisDomain } from "../../../components/chart/composite/types";
|
|
6
|
-
import { FUTURES_CONTRACTS, tickDecimals } from "
|
|
6
|
+
import { FUTURES_CONTRACTS, futuresBoardContractForTicker, futuresContractByCode, tickDecimals } from "../../../market-data/futures-board";
|
|
7
7
|
import { formatPercentileRank } from "../../../utils/format";
|
|
8
8
|
import { cryptoPairCoin, isFuturesSymbol } from "../shared/crypto-pair";
|
|
9
9
|
import { parseReportTime } from "../../../utils/utc-time";
|
|
@@ -21,7 +21,7 @@ const CRYPTO_CURVE_ROOTS: readonly { code: string; name: string; tick: number }[
|
|
|
21
21
|
const rootLabel = (code: string, name: string) => name === code ? code : `${code} ${name}`;
|
|
22
22
|
|
|
23
23
|
export const CURVE_ROOTS = [
|
|
24
|
-
...FUTURES_CONTRACTS.
|
|
24
|
+
...FUTURES_CONTRACTS.map((row) => ({ value: row.code, label: rootLabel(row.code, row.name) })),
|
|
25
25
|
{ value: "VX", label: "VX VIX Futures" },
|
|
26
26
|
...CRYPTO_CURVE_ROOTS.map((row) => ({ value: row.code, label: rootLabel(row.code, row.name) })),
|
|
27
27
|
];
|
|
@@ -34,24 +34,40 @@ export function normalizeCurveRoot(value: unknown): string | null {
|
|
|
34
34
|
return CURVE_ROOTS.some((row) => row.value === root) ? root : null;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
/** What a root
|
|
37
|
+
/** What a root CTM does not list is told: the roots that work, with the FUT board's, VIX and CME crypto named by group. */
|
|
38
38
|
export function unsupportedCurveRootMessage(value: unknown): string {
|
|
39
|
+
const typed = String(value).trim().toUpperCase().replace(/=F$/, "");
|
|
39
40
|
const examples = ["ES", "CL", "GC", "ZN"].filter((root) => CURVE_ROOTS.some((row) => row.value === root));
|
|
40
|
-
return `
|
|
41
|
+
return `No futures curve for ${typed || "that root"}: CTM covers the FUT board's roots, VIX and CME crypto, such as ${examples.join(", ")}.`;
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Board roots whose bare code opens their curve from the ticker under the
|
|
46
|
+
* cursor. Several are also listed securities (ES Eversource, NG NovaGold, PL
|
|
47
|
+
* Planet Labs), but they followed the cursor before the board was checked
|
|
48
|
+
* against listed securities and keep doing so. A root added after that (ZM is
|
|
49
|
+
* Zoom, KE, LE, HE and ALI are tickers too) follows the cursor only as its
|
|
50
|
+
* `=F` symbol or a code no security answers to (`bareCode`: ZL, OJ, LBR), the
|
|
51
|
+
* rule FUT and COT apply, so a new board row defaults to the security.
|
|
52
|
+
*/
|
|
53
|
+
const CURSOR_BARE_ROOTS: ReadonlySet<string> = new Set(["ES", "CL", "BZ", "NG", "RB", "HO", "GC", "SI", "HG", "PL", "PA", "ZC", "ZS", "KC", "SB", "CC"]);
|
|
54
|
+
|
|
43
55
|
/**
|
|
44
56
|
* The root for the ticker under the cursor, when CTM is opened without an
|
|
45
57
|
* argument. The crypto roots are also US ticker symbols (Grayscale's mini
|
|
46
58
|
* trusts are BTC and ETH), so a bare one is the equity's, never the future's:
|
|
47
59
|
* only a futures symbol (BTC=F) or a coin pair (BTC-USD) names the crypto curve.
|
|
60
|
+
* A typed argument is different: `CTM ZM` is soybean meal even though ZM is also Zoom.
|
|
48
61
|
*/
|
|
49
62
|
export function curveRootForTicker(ticker: unknown): string | null {
|
|
50
63
|
if (typeof ticker !== "string") return null;
|
|
51
64
|
const coin = cryptoPairCoin(ticker);
|
|
52
65
|
if (coin) return CRYPTO_CURVE_ROOTS.some((row) => row.code === coin) ? coin : null;
|
|
53
66
|
const root = normalizeCurveRoot(ticker);
|
|
54
|
-
|
|
67
|
+
if (!root) return null;
|
|
68
|
+
if (CRYPTO_CURVE_ROOTS.some((row) => row.code === root)) return isFuturesSymbol(ticker) ? root : null;
|
|
69
|
+
const isBoardRoot = futuresContractByCode(root) != null;
|
|
70
|
+
return isBoardRoot && !CURSOR_BARE_ROOTS.has(root) ? futuresBoardContractForTicker(ticker)?.code ?? null : root;
|
|
55
71
|
}
|
|
56
72
|
|
|
57
73
|
/**
|
|
@@ -67,7 +83,7 @@ function curvePriceDecimals(root: string): number {
|
|
|
67
83
|
const rateTick = RATE_TICKS[root];
|
|
68
84
|
if (rateTick != null) return tickDecimals(rateTick);
|
|
69
85
|
if (root === "VX") return 4;
|
|
70
|
-
const tick =
|
|
86
|
+
const tick = futuresContractByCode(root)?.tick ?? CRYPTO_CURVE_ROOTS.find((row) => row.code === root)?.tick;
|
|
71
87
|
return tick == null ? 5 : Math.max(2, tickDecimals(tick));
|
|
72
88
|
}
|
|
73
89
|
|
|
@@ -84,6 +100,39 @@ export function curveChangeText(value: number | null, root: string): string {
|
|
|
84
100
|
return value != null && value > 0 && /[1-9]/.test(text) ? `+${text}` : text;
|
|
85
101
|
}
|
|
86
102
|
|
|
103
|
+
/** A price as a number at the root's display precision, for reports: 59.299805 reads 59.3, never float noise. */
|
|
104
|
+
export function curveRoundedPrice(value: number | null | undefined, root: string): number | null {
|
|
105
|
+
return value == null || !Number.isFinite(value) ? null : Number(value.toFixed(curvePriceDecimals(root)));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The unit the catalogue gives a root whose contract terms the server does not send: "EUR / MWh" for Dutch TTF gas. */
|
|
109
|
+
export function catalogueCurveUnit(root: string): string | null {
|
|
110
|
+
return futuresContractByCode(root)?.unit ?? null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* What a price counts: the exchange's unit ("USD / troy oz") where the root's
|
|
115
|
+
* terms are known, the catalogue's where they are not, else the quote unit.
|
|
116
|
+
*/
|
|
117
|
+
export function curveUnitLabel(data: Pick<FuturesCurvePayload, "root" | "spec" | "quoteUnit" | "currency">): string | null {
|
|
118
|
+
return data.spec?.unit ?? catalogueCurveUnit(data.root) ?? data.quoteUnit ?? data.currency ?? null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** A contract as traders write it: GCZ26.CMX is GCZ26; a VX symbol stays as Cboe lists it. */
|
|
122
|
+
export function curveContractCode(symbol: string): string {
|
|
123
|
+
return symbol.replace(/\.[A-Z]+$/, "");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The front spread named by the contracts it subtracts, "GCF27-GCZ26": the
|
|
128
|
+
* next contract less the front one. The front is the first contract the
|
|
129
|
+
* market trades, which can be past a delivery month nobody holds, so a bare
|
|
130
|
+
* "M2-M1" would not say which rows it means.
|
|
131
|
+
*/
|
|
132
|
+
export function curveSpreadLabel(slope: Pick<FuturesCurvePayload["slope"], "frontSymbol" | "nextSymbol">): string {
|
|
133
|
+
return slope.frontSymbol && slope.nextSymbol ? `${curveContractCode(slope.nextSymbol)}-${curveContractCode(slope.frontSymbol)}` : "M2-M1";
|
|
134
|
+
}
|
|
135
|
+
|
|
87
136
|
/**
|
|
88
137
|
* Axis gridlines sit on round values, so they need no tick precision: one
|
|
89
138
|
* decimal count across the gutter, as many as the plotted range asks for and
|
|
@@ -188,13 +237,14 @@ export function curveContractChanges(data: FuturesCurvePayload): CurveContractCh
|
|
|
188
237
|
|
|
189
238
|
const CHANGE_COLUMNS: Readonly<Record<string, CurveLookback>> = { change1w: "1W", change1m: "1M" };
|
|
190
239
|
|
|
191
|
-
type CurveSortKey = "symbol" | "expiration" | "price" | "change" | "openInterest" | "volume" | "percentile" | "asOf";
|
|
240
|
+
type CurveSortKey = "symbol" | "expiration" | "price" | "settlement" | "firstNotice" | "change" | "openInterest" | "volume" | "percentile" | "asOf";
|
|
192
241
|
|
|
193
242
|
const BASIS_COLUMNS: Readonly<Record<string, "vsSpotPct" | "annualisedBasisPct">> = { vsSpot: "vsSpotPct", annBasis: "annualisedBasisPct" };
|
|
194
243
|
|
|
195
244
|
export function sortCurveContracts(rows: readonly FuturesContract[], id: string, direction: SortDirection,
|
|
196
245
|
changes?: CurveContractChanges, basis?: CurveBasisRows): FuturesContract[] {
|
|
197
|
-
const keys: Record<string, CurveSortKey> = { symbol: "symbol", expiry: "expiration", price: "price",
|
|
246
|
+
const keys: Record<string, CurveSortKey> = { symbol: "symbol", expiry: "expiration", price: "price", settle: "settlement", notice: "firstNotice",
|
|
247
|
+
change: "change", oi: "openInterest", volume: "volume", percentile: "percentile", asOf: "asOf" };
|
|
198
248
|
const key = keys[id] ?? "expiration";
|
|
199
249
|
const lookback = CHANGE_COLUMNS[id];
|
|
200
250
|
const basisKey = BASIS_COLUMNS[id];
|
|
@@ -399,6 +449,9 @@ export function archivedFuturesCurve(root: string, curve: FuturesCurveAsOfPayloa
|
|
|
399
449
|
symbol: row.symbol, label: row.label, expiration: row.expiration, price: row.price, change: null, asOf: rowDate(row, stale),
|
|
400
450
|
currency: curve.currency ?? "USD", quoteUnit: curve.quoteUnit ?? curve.currency ?? "USD", volume: row.volume,
|
|
401
451
|
openInterest: row.openInterest, delayMinutes: null, stale, percentile: null, samples: 0, historyStart: null, historyEnd: null,
|
|
452
|
+
// Only a row the archive confirms is a settlement; an unconfirmed one is that session's last trade.
|
|
453
|
+
settlement: !stale && row.settled === true ? row.price : null,
|
|
454
|
+
lastTrade: row.lastTrade ?? row.expiration, firstNotice: row.firstNotice ?? null, inDelivery: row.inDelivery ?? false,
|
|
402
455
|
}];
|
|
403
456
|
}).sort((a, b) => a.expiration.localeCompare(b.expiration));
|
|
404
457
|
const ghosts = (["1W", "1M"] as const).map((label) => {
|
|
@@ -407,8 +460,10 @@ export function archivedFuturesCurve(root: string, curve: FuturesCurveAsOfPayloa
|
|
|
407
460
|
points: past ? past.contracts.flatMap((row) => row.expiration
|
|
408
461
|
? [{ symbol: row.symbol, expiration: row.expiration, price: row.price, asOf: rowDate(row, archivedRowStale(row, past)) }] : []) : [] };
|
|
409
462
|
});
|
|
410
|
-
// The front pair on the curve's own session
|
|
411
|
-
|
|
463
|
+
// The front pair on the curve's own session: settlements where the archive has them, else that session's
|
|
464
|
+
// last trades. Carried prices, and a last trade beside a settlement, do not make a spread.
|
|
465
|
+
const settled = contracts.filter((row) => row.settlement != null);
|
|
466
|
+
const [front, next] = settled.length >= 2 ? settled : settled.length ? [] : contracts.filter((row) => !row.stale);
|
|
412
467
|
const value = front && next ? next.price! - front.price! : null;
|
|
413
468
|
const days = front && next ? (Date.parse(next.expiration) - Date.parse(front.expiration)) / DAY_MS : 0;
|
|
414
469
|
const roll = front && next && front.price! > 0 && next.price! > 0 && days > 0 ? (front.price! / next.price! - 1) * 365 / days * 100 : null;
|
|
@@ -419,6 +474,6 @@ export function archivedFuturesCurve(root: string, curve: FuturesCurveAsOfPayloa
|
|
|
419
474
|
slope: { frontSymbol: front?.symbol ?? null, nextSymbol: next?.symbol ?? null, value, annualizedRollYield: roll, percentile: null,
|
|
420
475
|
rollPercentile: null, samples: 0, historyStart: null, historyEnd: null, asOf: front && next ? curve.asOf : null,
|
|
421
476
|
state: value == null ? "unavailable" : Math.abs(value) < 1e-10 ? "flat" : value > 0 ? "contango" : "backwardation" },
|
|
422
|
-
gaps: curve.gaps,
|
|
477
|
+
gaps: curve.gaps, spec: curve.spec ?? null, settlementDate: settled.length ? curve.asOf : null,
|
|
423
478
|
};
|
|
424
479
|
}
|
|
@@ -4,6 +4,7 @@ import { fitChartTableColumns } from "../../../components/chart-table";
|
|
|
4
4
|
import { curveStrip, curveSurfaceMinRows } from "../../../components/chart/curve";
|
|
5
5
|
import { isAccessDenied } from "../../../api-client/errors";
|
|
6
6
|
import type { FuturesContract } from "../../../api-client/futures-curve";
|
|
7
|
+
import { futuresTickText } from "../../../market-data/futures-contract";
|
|
7
8
|
import { useAsyncResource, usePaneSettingValue, usePluginPaneState, useShortcut } from "../../../public/react";
|
|
8
9
|
import { usePaneInstance, usePaneTitle } from "../../../state/app/context";
|
|
9
10
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
@@ -16,14 +17,19 @@ import { futuresSessionRefreshInterval } from "../shared/futures-session";
|
|
|
16
17
|
import { useResearchCloudSession } from "../shared/research-cloud-session";
|
|
17
18
|
import { useQuoteBoard } from "../shared/use-quote-board";
|
|
18
19
|
import { getCachedFuturesCurve, loadFuturesCurve, loadFuturesCurveAsOf } from "./client";
|
|
19
|
-
import { basisSpotSymbol, curveAsOfDate, curveAxisPrice, curveBasisPercent, curveBasisRows, curveChangeText, curveContractChanges, curvePrice, curveRank, curveSpot, curveSpotLabel, curveTimestamp, DEFAULT_CURVE_HORIZON, futuresCurveSeries, newestQuote, normalizeCurveRoot, sortCurveContracts, thinContractCount, thinContractsNotice, unsupportedCurveRootMessage, type CurveContractChanges } from "./model";
|
|
20
|
+
import { basisSpotSymbol, curveAsOfDate, curveAxisPrice, curveBasisPercent, curveBasisRows, curveChangeText, curveContractChanges, curveContractCode, curvePrice, curveRank, curveSpot, curveSpotLabel, curveSpreadLabel, curveTimestamp, curveUnitLabel, DEFAULT_CURVE_HORIZON, futuresCurveSeries, newestQuote, normalizeCurveRoot, sortCurveContracts, thinContractCount, thinContractsNotice, unsupportedCurveRootMessage, type CurveContractChanges } from "./model";
|
|
20
21
|
|
|
21
22
|
const TABS = [{ value: "curve", label: "Curve" }, { value: "contracts", label: "Contracts" }];
|
|
23
|
+
// One column order for every root. SETTLE is the exchange settlement for the
|
|
24
|
+
// curve's settlement session, LAST the latest trade; the header dates SETTLE.
|
|
22
25
|
const COLUMNS: DataTableColumn[] = [
|
|
23
26
|
// The longest symbol is a three-letter root with its month and venue: RTYH27.CME.
|
|
24
27
|
{ id: "symbol", label: "CONTRACT", width: 11, align: "left" },
|
|
25
28
|
{ id: "expiry", label: "EXPIRY", width: 10, align: "left" },
|
|
26
|
-
{ id: "
|
|
29
|
+
{ id: "notice", label: "1ST NOTICE", width: 10, align: "left" },
|
|
30
|
+
// A Treasury price keeps its 1/256 tick: 103.50000000.
|
|
31
|
+
{ id: "settle", label: "SETTLE", width: 12, align: "right" },
|
|
32
|
+
{ id: "price", label: "LAST", width: 12, align: "right" },
|
|
27
33
|
// A Treasury change keeps its 1/256 tick: +0.11718750.
|
|
28
34
|
{ id: "change", label: "CHG", width: 11, align: "right" },
|
|
29
35
|
{ id: "percentile", label: "PCTL", width: 5, align: "right" },
|
|
@@ -31,25 +37,28 @@ const COLUMNS: DataTableColumn[] = [
|
|
|
31
37
|
{ id: "volume", label: "VOLUME", width: 10, align: "right" },
|
|
32
38
|
{ id: "asOf", label: "AS OF UTC", width: 16, align: "left" },
|
|
33
39
|
];
|
|
40
|
+
const CONTRACTS_DROP_ORDER = ["percentile", "volume", "oi", "notice"];
|
|
41
|
+
const column = (id: string) => COLUMNS.find((entry) => entry.id === id)!;
|
|
34
42
|
// The curve's rows move with the look-back curves drawn above them; the
|
|
35
43
|
// footer carries the quote time every row would otherwise repeat. The session
|
|
36
44
|
// change stays on Contracts, where the full table has the width for it.
|
|
37
45
|
const CURVE_COLUMNS: DataTableColumn[] = [
|
|
38
|
-
|
|
46
|
+
column("symbol"), column("expiry"), column("settle"), column("price"),
|
|
39
47
|
{ id: "change1w", label: "VS 1W", width: 10, align: "right" },
|
|
40
48
|
{ id: "change1m", label: "VS 1M", width: 10, align: "right" },
|
|
41
|
-
|
|
49
|
+
column("percentile"), column("oi"), column("volume"),
|
|
42
50
|
];
|
|
51
|
+
const CURVE_DROP_ORDER = ["percentile", "volume", "change1m", "oi"];
|
|
43
52
|
// A crypto curve also reads against spot. Its widths are the tightest each column
|
|
44
53
|
// holds (BTCV26.CME, 128000.00), so the two new columns fit beside the rest, and
|
|
45
54
|
// a narrow pane drops the least useful ones before a number would be clipped.
|
|
46
55
|
const BASIS_CURVE_COLUMNS: DataTableColumn[] = [
|
|
47
|
-
{ ...
|
|
56
|
+
{ ...column("symbol"), width: 10 }, column("expiry"), { ...column("settle"), width: 10 }, { ...column("price"), width: 10 },
|
|
48
57
|
{ id: "vsSpot", label: "VS SPOT", width: 9, align: "right" },
|
|
49
58
|
{ id: "annBasis", label: "ANN BASIS", width: 11, align: "right" },
|
|
50
|
-
...CURVE_COLUMNS.slice(
|
|
59
|
+
...CURVE_COLUMNS.slice(4),
|
|
51
60
|
];
|
|
52
|
-
const BASIS_DROP_ORDER = ["percentile", "volume", "change1m", "oi"];
|
|
61
|
+
const BASIS_DROP_ORDER = ["percentile", "volume", "change1m", "oi", "settle"];
|
|
53
62
|
const NO_SYMBOLS: string[] = [];
|
|
54
63
|
/** The spot's age is judged against the clock, so a quote that stops moving still goes stale on screen. */
|
|
55
64
|
const SPOT_CLOCK_MS = 30_000;
|
|
@@ -135,17 +144,31 @@ function FuturesCurveView({ width, height, focused, root }: PaneProps & { root:
|
|
|
135
144
|
const statItems: StatItem[] = data ? [
|
|
136
145
|
{ id: "roll", label: "Ann. roll yield", value: signedPercent(data.slope.annualizedRollYield),
|
|
137
146
|
detail: curveRank(data.slope.rollPercentile, data.slope.samples) },
|
|
138
|
-
|
|
147
|
+
// Named by the contracts it subtracts: the front is the first month the market trades, not always the first row.
|
|
148
|
+
{ id: "spread", label: curveSpreadLabel(data.slope), value: data.slope.value == null ? "--" : curvePrice(data.slope.value, root),
|
|
139
149
|
detail: [data.slope.state, curveRank(data.slope.percentile, data.slope.samples), slopeDate].filter(Boolean).join(" · ") },
|
|
150
|
+
...data.spec ? [{ id: "contract", label: "Contract", value: data.spec.size, detail: `tick ${futuresTickText(data.spec)}` }] : [],
|
|
140
151
|
] : [];
|
|
141
152
|
const { strip: tabStrip, rows: tabRows } = usePaneTabs({ tabs: TABS, activeValue: tab, onSelect: setTab, focused, dense: true });
|
|
142
153
|
// The query bar takes one row below the tabs, and a refused date one more.
|
|
143
154
|
const bodyHeight = Math.max(1, height - tabRows - 1 - (dateError ? 1 : 0));
|
|
144
|
-
|
|
145
|
-
|
|
155
|
+
// A Cboe price is the settlement itself, so its curve has no separate last trade.
|
|
156
|
+
const settlesOnly = data?.source === "cboe";
|
|
157
|
+
const settleLabel = data?.settlementDate ? `SETTLE ${data.settlementDate.slice(5)}` : "SETTLE";
|
|
158
|
+
// Cash-settled roots (ES, CL's Brent, VX) have no first notice day to show.
|
|
159
|
+
const noNotice = !data?.contracts.some((row) => row.firstNotice);
|
|
160
|
+
const columns = useMemo(() => {
|
|
161
|
+
// Labelled before fitting: a dated header is wider than the column's numbers.
|
|
162
|
+
const shown = (list: DataTableColumn[]) => list.filter((entry) => !(settlesOnly && entry.id === "price") && !(noNotice && entry.id === "notice"))
|
|
163
|
+
.map((entry) => entry.id === "settle" ? { ...entry, label: settleLabel } : entry);
|
|
164
|
+
return !curveTab ? fitChartTableColumns(shown(COLUMNS), width, CONTRACTS_DROP_ORDER)
|
|
165
|
+
: basisActive ? fitChartTableColumns(shown(BASIS_CURVE_COLUMNS), width, BASIS_DROP_ORDER)
|
|
166
|
+
: fitChartTableColumns(shown(CURVE_COLUMNS), width, CURVE_DROP_ORDER);
|
|
167
|
+
}, [basisActive, curveTab, noNotice, settleLabel, settlesOnly, width]);
|
|
146
168
|
const formatValue = useCallback((value: number) => curvePrice(value, root), [root]);
|
|
147
169
|
const formatChange = useCallback((value: number) => curveChangeText(value, root), [root]);
|
|
148
|
-
const
|
|
170
|
+
const unit = data ? curveUnitLabel(data) : null;
|
|
171
|
+
const caption = `${sentenceCase(unit ?? "price")} by contract month`;
|
|
149
172
|
// The strip keeps the curve's shape and the selected contract in one row.
|
|
150
173
|
const strip = curveTab ? curveStrip(curves, formatValue, { caption: "Price", selectedPointId: selectedId }) : null;
|
|
151
174
|
// Delayed contract quotes move all session; the curve follows them once a
|
|
@@ -177,8 +200,11 @@ function FuturesCurveView({ width, height, focused, root }: PaneProps & { root:
|
|
|
177
200
|
// A past date with no archived curve says why in the body, not behind the warning.
|
|
178
201
|
const emptyPast = !!requestedDate && !!data && !data.contracts.length;
|
|
179
202
|
// Thin contracts join the data warnings: the info row has no room for them beside the quote times.
|
|
203
|
+
// A long in these can be assigned delivery: the hedge a trader rolls before first notice.
|
|
204
|
+
const delivering = data?.contracts.filter((row) => row.inDelivery).map((row) => curveContractCode(row.symbol)) ?? [];
|
|
180
205
|
usePaneNoticeFooter({ registrationId: "futures-curve:notices", focused,
|
|
181
|
-
notices: emptyPast ? [] : [...data?.gaps ?? [], ...thinCount ? [thinContractsNotice(thinCount)] : []
|
|
206
|
+
notices: emptyPast ? [] : [...data?.gaps ?? [], ...thinCount ? [thinContractsNotice(thinCount)] : [],
|
|
207
|
+
...delivering.length ? [`${delivering.join(", ")} ${delivering.length === 1 ? "is" : "are"} past first notice.`] : []] });
|
|
182
208
|
const delay = Math.max(0, ...(data?.contracts.map((row) => row.delayMinutes ?? 0) ?? []));
|
|
183
209
|
usePaneStatusFooter({ registrationId: "futures-curve", loading: resource.loading, error: resource.error,
|
|
184
210
|
hints: [
|
|
@@ -186,7 +212,7 @@ function FuturesCurveView({ width, height, focused, root }: PaneProps & { root:
|
|
|
186
212
|
...requestedDate ? [{ id: "latest", key: "c", label: "urrent", title: "Current Curve", onPress: () => selectDate("") }] : [],
|
|
187
213
|
],
|
|
188
214
|
info: data ? [
|
|
189
|
-
{ id: "source", parts: [{ text: `${requestedDate ? "daily archive" : data.source === "cboe" ? "settlement" : delay > 0 ? `${delay}m delayed` : "dated quotes"} · ${
|
|
215
|
+
{ id: "source", parts: [{ text: `${requestedDate ? "daily archive" : data.source === "cboe" ? "settlement" : delay > 0 ? `${delay}m delayed` : "dated quotes"} · ${unit ?? "units unavailable"} · ${curveTimestamp(newest)}${newest?.includes("T") ? " UTC" : ""}`, tone: "muted" }] },
|
|
190
216
|
...(staleCount ? [{ id: "stale", parts: [{ text: `${staleCount} of ${data.contracts.length} stale`, tone: "warning" as const }] }] : []),
|
|
191
217
|
...(spot ? [{ id: "spot", parts: [spot.status === "ok" ? { text: curveSpotLabel(spot, root, clock), tone: "muted" as const }
|
|
192
218
|
: { text: `Basis blank: ${spot.reason}`, tone: "warning" as const }] }] : []),
|
|
@@ -202,10 +228,18 @@ function FuturesCurveView({ width, height, focused, root }: PaneProps & { root:
|
|
|
202
228
|
strip,
|
|
203
229
|
} : null;
|
|
204
230
|
const renderCell = useCallback((row: FuturesContract, column: DataTableColumn) => {
|
|
205
|
-
|
|
206
|
-
if (column.id === "
|
|
207
|
-
|
|
208
|
-
if (column.id === "
|
|
231
|
+
// A contract past first notice can be assigned delivery.
|
|
232
|
+
if (column.id === "symbol") return { text: row.symbol, color: row.inDelivery ? colors.warning : undefined };
|
|
233
|
+
if (column.id === "expiry") return { text: row.lastTrade ?? row.expiration, color: colors.textMuted };
|
|
234
|
+
if (column.id === "notice") return { text: row.firstNotice ?? "--", color: row.inDelivery ? colors.warning : colors.textMuted };
|
|
235
|
+
if (column.id === "settle") return { text: curvePrice(row.settlement ?? null, root) };
|
|
236
|
+
// A past curve's settled row has no separate last trade: its price is the settlement.
|
|
237
|
+
if (column.id === "price") {
|
|
238
|
+
const lastTrade = requestedDate && row.settlement != null ? null : row.price;
|
|
239
|
+
// Beside its settlement the last trade is the secondary price; without one it is the row's price.
|
|
240
|
+
return { text: curvePrice(lastTrade, root), color: curveTab && (row.stale || basis.get(row.symbol)?.thin) ? colors.warning
|
|
241
|
+
: row.settlement != null ? colors.textMuted : undefined };
|
|
242
|
+
}
|
|
209
243
|
if (column.id === "change") {
|
|
210
244
|
const text = curveChangeText(row.change ?? null, root);
|
|
211
245
|
return { text, color: text.startsWith("+") ? colors.positive : text.startsWith("-") && /[1-9]/.test(text) ? colors.negative : colors.textMuted };
|
|
@@ -222,7 +256,7 @@ function FuturesCurveView({ width, height, focused, root }: PaneProps & { root:
|
|
|
222
256
|
if (column.id === "oi") return { text: integer(row.openInterest) };
|
|
223
257
|
if (column.id === "volume") return { text: integer(row.volume) };
|
|
224
258
|
return { text: curveTimestamp(row.asOf), color: row.stale ? colors.warning : colors.textMuted };
|
|
225
|
-
}, [basis, changes, colors, curveTab, root]);
|
|
259
|
+
}, [basis, changes, colors, curveTab, requestedDate, root]);
|
|
226
260
|
return <Box width={width} height={height} flexDirection="column">
|
|
227
261
|
{tabStrip}
|
|
228
262
|
<QueryBar width={width} filters={[{
|
|
@@ -20,6 +20,7 @@ import { TextAttributes, useUiCapabilities } from "../../../ui";
|
|
|
20
20
|
import { useAssetData } from "../../runtime";
|
|
21
21
|
import { summarizeFxRates, fxStatusLabel } from "../../../utils/fx-status";
|
|
22
22
|
import type { PluginModule } from "../plugin-module";
|
|
23
|
+
import { fxRateObservations } from "../shared/fx-rate-freshness";
|
|
23
24
|
import { useLiveQuoteEntries } from "../../../state/hooks/quote-streaming";
|
|
24
25
|
import { useAutoRefresh, useUpdatedAgo } from "../../../react/auto-refresh";
|
|
25
26
|
import { useLiveStreamingSetting } from "../../../state/hooks/live-streaming";
|
|
@@ -28,7 +29,6 @@ import { CURRENCY_FLAG_REGIONS, FX_CURRENCIES, formatRate, resolveCurrencies, ty
|
|
|
28
29
|
import { crossMovePercent, directionTint, nextTintLevel } from "./direction";
|
|
29
30
|
import { FX_MATRIX_REPORT_OPTIONS, fxMatrixReportNotices, fxMatrixSettings } from "./settings";
|
|
30
31
|
import { createFxExportMetadata } from "./export";
|
|
31
|
-
import { fxRateObservations } from "./freshness";
|
|
32
32
|
|
|
33
33
|
const FX_MATRIX_PANE_ID = "fx-matrix";
|
|
34
34
|
/** Stable identity: a fresh literal here would reload the board every render. */
|
|
@@ -12,7 +12,7 @@ import { Box, Text, TextAttributes, useUiCapabilities } from "../../../ui";
|
|
|
12
12
|
import { loadGpuHistory } from "./client";
|
|
13
13
|
import { GpuPriceLadder } from "./ladder";
|
|
14
14
|
import {
|
|
15
|
-
gpuAvailability, gpuBoardSections, gpuChange, gpuChangeColor, gpuChangeWindows, gpuHeadline, gpuPrice, gpuPriceLadder, gpuShortSource, gpuSparklineSeries, gpuTime, gpuVariant,
|
|
15
|
+
gpuAvailability, gpuBoardSections, gpuChange, gpuChangeColor, gpuChangesLocked, gpuChangeWindows, gpuHeadline, gpuPrice, gpuPriceLadder, gpuShortSource, gpuSparklineSeries, gpuTime, gpuVariant,
|
|
16
16
|
type GpuChangeKey,
|
|
17
17
|
} from "./model";
|
|
18
18
|
|
|
@@ -23,10 +23,10 @@ const CHANGE_LABELS: Record<GpuChangeKey, string> = { change1d: "1D", change7d:
|
|
|
23
23
|
/** A line needs three observations; two are a step, not a trend. */
|
|
24
24
|
const MIN_TREND_POINTS = 3;
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
/** The GPU picker: the GPUs on the board, or on a free board every GPU, since each has its own free preview. */
|
|
27
|
+
export function GpuModelQuery({ models, model, setModel, width, allowAll = true }: {
|
|
28
|
+
models: readonly string[]; model: string; setModel: (value: string) => void; width: number; allowAll?: boolean;
|
|
28
29
|
}) {
|
|
29
|
-
const models = useMemo(() => [...new Set(rows.map((row) => row.gpuModel))].sort(), [rows]);
|
|
30
30
|
return <QueryBar width={width} filters={[{ id: "gpu", label: "GPU", value: model, ...(allowAll ? { defaultValue: "" } : {}),
|
|
31
31
|
options: [...(allowAll ? [{ value: "", label: "All GPUs" }] : []), ...models.map((value) => ({ value, label: value }))], onChange: setModel }]} />;
|
|
32
32
|
}
|
|
@@ -79,9 +79,9 @@ function boardColumns(width: number, options: { windows: GpuChangeKey[]; trend:
|
|
|
79
79
|
];
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
export function GpuBoard({ rows, asOf, model, setModel, selectedId, select, width, height, focused }: {
|
|
83
|
-
rows: GpuBoardRow[]; asOf: string | null; model: string; setModel: (value: string) => void; selectedId: string; select: (row: GpuBoardRow, open?: boolean) => void;
|
|
84
|
-
width: number; height: number; focused: boolean;
|
|
82
|
+
export function GpuBoard({ rows, models, asOf, model, setModel, selectedId, select, emptyTitle, width, height, focused }: {
|
|
83
|
+
rows: GpuBoardRow[]; models: readonly string[]; asOf: string | null; model: string; setModel: (value: string) => void; selectedId: string; select: (row: GpuBoardRow, open?: boolean) => void;
|
|
84
|
+
emptyTitle: string; width: number; height: number; focused: boolean;
|
|
85
85
|
}) {
|
|
86
86
|
const colors = useThemeColors();
|
|
87
87
|
const sections = useMemo(() => gpuBoardSections(rows, model), [rows, model]);
|
|
@@ -95,6 +95,8 @@ export function GpuBoard({ rows, asOf, model, setModel, selectedId, select, widt
|
|
|
95
95
|
const medium = width >= 100;
|
|
96
96
|
const asOfDay = asOf?.slice(0, 10) ?? null;
|
|
97
97
|
const columns = boardColumns(width, { windows, trend, range: visible.some((row) => row.stats), availability: visible.some((row) => row.availability) });
|
|
98
|
+
// A free preview row's moves are Pro's; the cell says so, and the footer's upgrade hint unlocks them.
|
|
99
|
+
const lockedCell: DataTableCell = { text: "Pro", value: null, color: colors.textDim };
|
|
98
100
|
|
|
99
101
|
const renderCell = (item: BoardRow, column: DataTableColumn, _index: number, state: { selected: boolean }): DataTableCell => {
|
|
100
102
|
if (!isSectionedItemRow(item)) return EMPTY_TABLE_CELL;
|
|
@@ -121,8 +123,9 @@ export function GpuBoard({ rows, asOf, model, setModel, selectedId, select, widt
|
|
|
121
123
|
if (!quartiles && stats.min === stats.max) return { text: "" };
|
|
122
124
|
return { text: quartiles ? `${gpuPrice(stats.p25)}–${gpuPrice(stats.p75)}` : `${gpuPrice(stats.min)}–${gpuPrice(stats.max)}`, color: colors.textDim };
|
|
123
125
|
}
|
|
124
|
-
case "unavailableChange": return { text: "-", value: null, color: colors.textMuted };
|
|
126
|
+
case "unavailableChange": return gpuChangesLocked(row) ? lockedCell : { text: "-", value: null, color: colors.textMuted };
|
|
125
127
|
case "change1d": case "change7d": case "change30d": {
|
|
128
|
+
if (gpuChangesLocked(row)) return lockedCell;
|
|
126
129
|
const value = row[column.id];
|
|
127
130
|
return value == null ? { text: "-", value: null, color: colors.textMuted } : { text: gpuChange(value), value, color: gpuChangeColor(value, colors) };
|
|
128
131
|
}
|
|
@@ -148,7 +151,7 @@ export function GpuBoard({ rows, asOf, model, setModel, selectedId, select, widt
|
|
|
148
151
|
}
|
|
149
152
|
};
|
|
150
153
|
|
|
151
|
-
const query = <GpuModelQuery
|
|
154
|
+
const query = <GpuModelQuery models={models} model={model} setModel={setModel} width={width} />;
|
|
152
155
|
return <DataTableView<BoardRow> columns={columns} items={items} rootWidth={width} rootHeight={height} focused={focused}
|
|
153
156
|
rootBefore={ladder.length ? <ChartTableHeader width={width} height={height} tableRows={items.length} tableColumns={columns} query={query}
|
|
154
157
|
chart={{ maxRows: ladder.length + 1, minRows: Math.min(3, ladder.length + 1), strip: null,
|
|
@@ -159,5 +162,5 @@ export function GpuBoard({ rows, asOf, model, setModel, selectedId, select, widt
|
|
|
159
162
|
onChange: (_id, row) => { if (row && isSectionedItemRow(row)) select(row.item); } }}
|
|
160
163
|
isNavigable={isSectionedItemRow} renderSectionHeader={renderSectionedRowHeader}
|
|
161
164
|
getItemKey={rowKey} onActivate={(row) => { if (isSectionedItemRow(row)) select(row.item, true); }} sortColumnId={null} sortDirection="asc"
|
|
162
|
-
selectedTextOverridesCellColor renderCell={renderCell} emptyStateTitle=
|
|
165
|
+
selectedTextOverridesCellColor renderCell={renderCell} emptyStateTitle={emptyTitle} />;
|
|
163
166
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useMemo } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { GpuEvent } from "../../../api-client/gpu";
|
|
3
3
|
import {
|
|
4
4
|
Badge, buildSectionedRows, DataTableView, EMPTY_TABLE_CELL, isSectionedItemRow, PaneStatusBody, renderSectionedRowHeader,
|
|
5
5
|
usePaneNoticeFooter, usePaneStatusFooter, type DataTableCell, type DataTableColumn, type SectionedRow,
|
|
@@ -30,8 +30,8 @@ function changeColumns(width: number, allModels: boolean, availabilityWidth: num
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/** Dated price moves, newest day first: provider, old to new, and the move as a chip coloured by its sign. */
|
|
33
|
-
export function GpuChanges({
|
|
34
|
-
|
|
33
|
+
export function GpuChanges({ models, model, setModel, reloadBoard, width, height, focused }: {
|
|
34
|
+
models: readonly string[]; model: string; setModel: (value: string) => void; reloadBoard: () => void; width: number; height: number; focused: boolean;
|
|
35
35
|
}) {
|
|
36
36
|
const colors = useThemeColors();
|
|
37
37
|
const loader = useCallback((force: boolean) => loadGpuEvents(model || undefined, force), [model]);
|
|
@@ -84,7 +84,7 @@ export function GpuChanges({ board, model, setModel, reloadBoard, width, height,
|
|
|
84
84
|
|
|
85
85
|
return <PaneStatusBody loading={resource.loading && !resource.data} error={!resource.data ? resource.error : null} subject="GPU price changes">
|
|
86
86
|
<DataTableView<ChangeRow> columns={columns} items={items} rootWidth={width} rootHeight={height} focused={focused}
|
|
87
|
-
rootBefore={<GpuModelQuery
|
|
87
|
+
rootBefore={<GpuModelQuery models={models} model={model} setModel={setModel} width={width} />}
|
|
88
88
|
selection={{ kind: "id", selectedId: selected && items.some((item) => item.key === selected) ? selected : firstId, getId: rowKey,
|
|
89
89
|
onChange: (id) => setSelected(String(id)) }}
|
|
90
90
|
isNavigable={isSectionedItemRow} renderSectionHeader={renderSectionedRowHeader}
|
|
@@ -4,7 +4,7 @@ import type { GpuBoardPayload, GpuEventsPayload, GpuHistoryPayload, GpuHistoryQu
|
|
|
4
4
|
import { createPluginCache } from "../../../data/plugin-cache";
|
|
5
5
|
import type { DataProvider } from "../../../types/data-provider";
|
|
6
6
|
import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
7
|
-
import { equityFiveDayReturn, GPU_EQUITIES } from "./model";
|
|
7
|
+
import { equityFiveDayReturn, GPU_EQUITIES, gpuPreviewRows, type GpuPreviewRow } from "./model";
|
|
8
8
|
|
|
9
9
|
export const GPU_NOT_AVAILABLE = "GPU rental prices are not available yet.";
|
|
10
10
|
const cacheOptions = { source: "gloom-cloud", schemaVersion: 3, policy: { staleMs: 5 * 60_000, expireMs: 7 * 86_400_000 } };
|
|
@@ -116,6 +116,24 @@ export const loadGpuHistory = (seriesId: string, force = false) => loadCloudReso
|
|
|
116
116
|
() => fetchGpuHistory({ seriesId, limit: 10_000 }), { force, validate: validateHistory });
|
|
117
117
|
export const loadGpuReferences = (force = false) => loadCloudResource(gpuHistoryCache, scopedKey("reference-catalog"),
|
|
118
118
|
() => fetchGpuHistory({ basis: "reference", limit: 10_000 }), { force, validate: validateHistory });
|
|
119
|
+
/**
|
|
120
|
+
* The free board is three rows of one GPU. The rows it leaves out for the
|
|
121
|
+
* GPUs a reader asks about come from each GPU's own free preview, the latest
|
|
122
|
+
* rows the history serves without an account. A Pro board needs none.
|
|
123
|
+
*/
|
|
124
|
+
export function gpuPreviewModels(board: Pick<GpuBoardPayload, "access" | "rows">, models: readonly string[]): string[] {
|
|
125
|
+
if (!board.access?.locked) return [];
|
|
126
|
+
return [...new Set(models)].filter((model) => model && !board.rows.some((row) => row.gpuModel === model));
|
|
127
|
+
}
|
|
128
|
+
export async function fetchGpuModelPreviews(models: readonly string[], client: Pick<GpuApi, "getCloudGpuHistory"> = apiClient): Promise<GpuPreviewRow[]> {
|
|
129
|
+
const previews = await Promise.all(models.map((gpuModel) => fetchGpuHistory({ gpuModel, limit: 10 }, client)));
|
|
130
|
+
return previews.flatMap((payload) => gpuPreviewRows(payload.points));
|
|
131
|
+
}
|
|
132
|
+
export const loadGpuModelPreviews = async (models: readonly string[], force = false): Promise<GpuPreviewRow[]> => {
|
|
133
|
+
const previews = await Promise.all(models.map((gpuModel) => loadCloudResource(gpuHistoryCache, scopedKey(`preview:${gpuModel}`),
|
|
134
|
+
() => fetchGpuHistory({ gpuModel, limit: 10 }), { force, validate: validateHistory })));
|
|
135
|
+
return previews.flatMap((preview) => gpuPreviewRows(preview.payload.points));
|
|
136
|
+
};
|
|
119
137
|
export const loadGpuEvents = (gpuModel?: string, force = false) => loadCloudResource(gpuEventsCache, scopedKey(gpuModel ?? "all"),
|
|
120
138
|
() => fetchGpuEvents(gpuModel), { force, validate: validateEvents });
|
|
121
139
|
|
|
@@ -16,11 +16,10 @@ import { displayWidth, truncateToDisplayWidth } from "../../../utils/format";
|
|
|
16
16
|
import { quoteBoardFooterInfo, quoteBoardStatus, renderQuoteBoardCell, useQuoteBoard } from "../shared/use-quote-board";
|
|
17
17
|
import { GpuModelQuery } from "./board";
|
|
18
18
|
import { loadGpuEquityHistory } from "./client";
|
|
19
|
-
import { GPU_EQUITIES, GPU_EQUITY_GROUPS, gpuChange, gpuChangeColor, gpuEquityRows, gpuPrice, gpuShortSource, gpuVariant } from "./model";
|
|
19
|
+
import { GPU_EQUITIES, GPU_EQUITY_GROUPS, gpuChange, gpuChangeColor, gpuChangesLocked, gpuEquityGap, gpuEquityRows, gpuPrice, gpuShortSource, gpuVariant, type GpuEquityRow } from "./model";
|
|
20
20
|
|
|
21
21
|
const SYMBOLS = GPU_EQUITIES.map(({ symbol }) => symbol);
|
|
22
|
-
type
|
|
23
|
-
type Row = SectionedRow<EquityRow>;
|
|
22
|
+
type Row = SectionedRow<GpuEquityRow>;
|
|
24
23
|
const rowKey = (row: Row) => row.key;
|
|
25
24
|
|
|
26
25
|
function equityColumns(width: number): DataTableColumn[] {
|
|
@@ -39,8 +38,9 @@ function equityColumns(width: number): DataTableColumn[] {
|
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
/** Related shares by what ties them to GPU rents, each beside the list price that ties it. */
|
|
42
|
-
export function GpuEquities({ board, model, setModel, reloadBoard, width, height, focused }: {
|
|
43
|
-
board: GpuBoardRow[]; model: string; setModel: (value: string) => void; reloadBoard: () => void;
|
|
41
|
+
export function GpuEquities({ board, models, locked, model, setModel, reloadBoard, width, height, focused }: {
|
|
42
|
+
board: GpuBoardRow[]; models: readonly string[]; locked: boolean; model: string; setModel: (value: string) => void; reloadBoard: () => void;
|
|
43
|
+
width: number; height: number; focused: boolean;
|
|
44
44
|
}) {
|
|
45
45
|
const colors = useThemeColors();
|
|
46
46
|
const provider = useAssetData();
|
|
@@ -54,7 +54,7 @@ export function GpuEquities({ board, model, setModel, reloadBoard, width, height
|
|
|
54
54
|
const history = useAsyncResource(loader);
|
|
55
55
|
useAutoRefresh(history.updatedAt, history.load);
|
|
56
56
|
usePaneRefreshKey(() => { reloadBoard(); refresh(); void history.reload(); }, { focused });
|
|
57
|
-
const rows = useMemo(() => gpuEquityRows(board, model || "H100"), [board, model]);
|
|
57
|
+
const rows = useMemo(() => gpuEquityRows(board, model || "H100", locked), [board, model, locked]);
|
|
58
58
|
const items = useMemo(() => buildSectionedRows(GPU_EQUITY_GROUPS.map((group) => ({ label: group.label,
|
|
59
59
|
items: rows.filter((row) => (group.roles as readonly string[]).includes(row.role)) })), (row) => row.symbol), [rows]);
|
|
60
60
|
const historyMap = useMemo(() => new Map(history.data?.map((entry) => [entry.symbol, entry]) ?? []), [history.data]);
|
|
@@ -70,10 +70,13 @@ export function GpuEquities({ board, model, setModel, reloadBoard, width, height
|
|
|
70
70
|
if (!isSectionedItemRow(item)) return EMPTY_TABLE_CELL;
|
|
71
71
|
const row = item.item;
|
|
72
72
|
const reference = row.reference;
|
|
73
|
+
// A figure the free preview leaves out reads Pro; a share with no series names the one it lacks.
|
|
74
|
+
const lockedCell: DataTableCell = { text: "Pro", value: null, color: colors.textDim };
|
|
75
|
+
const gapCell = row.gap === "pro" ? lockedCell : { text: "" };
|
|
73
76
|
switch (column.id) {
|
|
74
77
|
case "symbol": return { text: row.symbol, color: colors.textBright, attributes: TextAttributes.BOLD };
|
|
75
78
|
case "source": {
|
|
76
|
-
if (!reference) return { text: "", color: colors.textDim };
|
|
79
|
+
if (!reference) return { text: gpuEquityGap(row) ?? "", color: colors.textDim };
|
|
77
80
|
const name = gpuShortSource(reference);
|
|
78
81
|
const variant = [row.gpuModel, ...gpuVariant(reference)].join(" ");
|
|
79
82
|
// The name keeps its room; the variant gives way first.
|
|
@@ -83,9 +86,10 @@ export function GpuEquities({ board, model, setModel, reloadBoard, width, height
|
|
|
83
86
|
{rest >= 4 ? <Text fg={state.selected ? colors.selectedText : colors.textDim}>{` ${truncateToDisplayWidth(variant, rest)}`}</Text> : null}
|
|
84
87
|
</Box> };
|
|
85
88
|
}
|
|
86
|
-
case "gpuPrice": return reference ? { text: gpuPrice(reference.pricePerGpuHr), value: reference.pricePerGpuHr, color: colors.textBright } :
|
|
89
|
+
case "gpuPrice": return reference ? { text: gpuPrice(reference.pricePerGpuHr), value: reference.pricePerGpuHr, color: colors.textBright } : gapCell;
|
|
87
90
|
case "gpuChange": {
|
|
88
|
-
if (!reference) return
|
|
91
|
+
if (!reference) return gapCell;
|
|
92
|
+
if (gpuChangesLocked(reference)) return lockedCell;
|
|
89
93
|
const value = reference.change7d;
|
|
90
94
|
return value == null ? { text: "-", value: null, color: colors.textMuted } : { text: gpuChange(value), value, color: gpuChangeColor(value, colors) };
|
|
91
95
|
}
|
|
@@ -102,7 +106,7 @@ export function GpuEquities({ board, model, setModel, reloadBoard, width, height
|
|
|
102
106
|
}
|
|
103
107
|
}, [colors]);
|
|
104
108
|
return <DataTableView<Row> columns={equityColumns(width)} items={items} rootWidth={width} rootHeight={height} focused={focused}
|
|
105
|
-
rootBefore={<GpuModelQuery
|
|
109
|
+
rootBefore={<GpuModelQuery models={models} model={model || "H100"} setModel={setModel} width={width} allowAll={false} />}
|
|
106
110
|
selection={{ kind: "id", selectedId: selected, getId: rowKey, onChange: (id) => setSelected(String(id)) }} getItemKey={rowKey}
|
|
107
111
|
isNavigable={isSectionedItemRow} renderSectionHeader={renderSectionedRowHeader}
|
|
108
112
|
getRowVersion={(row) => isSectionedItemRow(row) ? [quotes.get(row.item.symbol)?.quote?.lastUpdated, quotes.get(row.item.symbol)?.loading,
|