gloomberb 0.19.1 → 0.19.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/README.md +7 -1
- package/package.json +2 -1
- package/patches/electrobun@1.18.1.patch +108 -1
- package/src/account/upgrade-dialog.tsx +4 -23
- package/src/api-client/coalesce.ts +118 -0
- package/src/api-client/credit-boards.ts +13 -0
- package/src/api-client/crypto-etf-flows.ts +6 -0
- package/src/api-client/crypto-markets.ts +40 -0
- package/src/api-client/data.ts +201 -14
- package/src/api-client/equity-screener.ts +10 -0
- package/src/api-client/errors.ts +58 -0
- package/src/api-client/gpu.ts +29 -0
- package/src/api-client/index.ts +5 -2
- package/src/api-client/plan-access.ts +10 -1
- package/src/api-client/plan-rules.ts +35 -0
- package/src/api-client/research-activity.ts +1 -32
- package/src/api-client/types.ts +21 -1
- package/src/app/attention-notice.tsx +94 -0
- package/src/app/runtime/startup.ts +3 -1
- package/src/app.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +2 -1
- package/src/capabilities/chart-series.ts +2 -1
- package/src/capabilities/factories.ts +1 -0
- package/src/cli/ascii-output.ts +42 -0
- package/src/cli/base-converter.ts +10 -2
- package/src/cli/command-options.ts +16 -1
- package/src/cli/commands/crypto-hints.ts +23 -4
- package/src/cli/commands/market.ts +101 -27
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +14 -10
- package/src/cli/commands/search.ts +84 -25
- package/src/cli/commands/system.ts +22 -30
- package/src/cli/commands/ticker.ts +49 -9
- package/src/cli/commands/version.ts +33 -0
- package/src/cli/context.ts +6 -9
- package/src/cli/data-dir.ts +7 -0
- package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
- package/src/cli/desktop-pane-shot-browser.ts +320 -0
- package/src/cli/desktop-pane-shot-routes.ts +26 -1
- package/src/cli/desktop-pane-shot.ts +196 -256
- package/src/cli/early-command.ts +42 -0
- package/src/cli/earnings-calendar.ts +9 -0
- package/src/cli/entry.ts +19 -13
- package/src/cli/errors.ts +12 -2
- package/src/cli/glossary.ts +2 -1
- package/src/cli/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +189 -11
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +51 -5
- package/src/cli/listing-arg.ts +63 -10
- package/src/cli/options.ts +1 -1
- package/src/cli/output-file.ts +60 -13
- package/src/cli/pane-functions/catalog.ts +14 -3
- package/src/cli/pane-functions/data.ts +1 -1
- package/src/cli/pane-functions/dom.ts +45 -0
- package/src/cli/pane-functions/freshness.ts +46 -9
- package/src/cli/pane-functions/function-help.ts +17 -9
- package/src/cli/pane-functions/headless.ts +22 -14
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +6 -2
- package/src/cli/pane-functions/resolver.ts +27 -5
- package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
- package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
- package/src/cli/pane-functions/screenshot-http.ts +70 -0
- package/src/cli/pane-functions/screenshot-render.ts +19 -5
- package/src/cli/quote-answer.ts +49 -6
- package/src/cli/registry.ts +41 -13
- package/src/cli/result.ts +12 -6
- package/src/cli/ticker-resolution.ts +2 -1
- package/src/cli/types.ts +2 -0
- package/src/components/chart/composite/format.ts +3 -0
- package/src/components/chart-table/header.tsx +1 -1
- package/src/components/chart-table/layout.ts +28 -11
- package/src/components/command-bar/action-runtime.ts +4 -0
- package/src/components/command-bar/commands/direct/index.ts +18 -33
- package/src/components/command-bar/commands/direct/runtime.ts +5 -0
- package/src/components/command-bar/commands/registry.ts +10 -1
- package/src/components/command-bar/routes/root/command-items.ts +9 -4
- package/src/components/command-bar/routes/root/runtime.ts +51 -6
- package/src/components/command-bar/selection-runtime.ts +12 -29
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/header.tsx +10 -5
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +8 -2
- package/src/components/layout/pane/footer/model.ts +73 -6
- package/src/components/layout/pane/header.tsx +2 -2
- package/src/components/layout/pane/status-footer.ts +2 -1
- package/src/components/market-board/index.tsx +3 -2
- package/src/components/onboarding/desks-step.tsx +12 -4
- package/src/components/onboarding/first-run-workspace.ts +81 -4
- package/src/components/onboarding/onboarding-frame.tsx +4 -2
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +60 -26
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
- package/src/components/onboarding/run-setup-again.ts +24 -0
- package/src/components/onboarding/use-onboarding-progress.ts +33 -15
- package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
- package/src/components/onboarding/wizard-model.ts +57 -3
- package/src/components/onboarding/wizard-positions.ts +1 -1
- package/src/components/ui/delayed-mark.tsx +56 -0
- package/src/components/ui/display.tsx +16 -5
- package/src/components/ui/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +16 -3
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/core/app-services.ts +11 -2
- package/src/data/app-persistence.ts +9 -7
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/plugin-cache.ts +15 -0
- package/src/data/plugin-state-store.ts +81 -24
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +165 -90
- package/src/data/session-store.ts +31 -8
- package/src/data/sqlite/access.ts +324 -0
- package/src/data/sqlite/checkpointer.ts +285 -0
- package/src/data/sqlite/database.ts +80 -4
- package/src/data/sqlite/retry.ts +50 -9
- package/src/data/ticker-store.ts +57 -23
- package/src/docs/snapshot.json +191 -78
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -2
- package/src/layout/desk-keys.ts +4 -1
- package/src/layout/desks.ts +38 -5
- package/src/layout-marketplace/gallery-desktop.tsx +45 -16
- package/src/market-data/batched-reads.ts +16 -0
- package/src/market-data/coordinator/auxiliary.ts +4 -2
- package/src/market-data/coordinator/index.ts +11 -2
- package/src/market-data/futures-board.ts +1 -1
- package/src/market-data/futures-contract.ts +3 -1
- package/src/market-data/market/index-level.ts +6 -4
- package/src/market-data/published-apac-sessions.ts +6 -0
- package/src/market-data/realized-volatility.ts +4 -2
- package/src/news/aggregator.ts +7 -4
- package/src/news/news-model.ts +11 -0
- package/src/news/story-id.ts +22 -0
- package/src/news/ticker-symbols.ts +17 -1
- package/src/notifications/app-notifier.ts +35 -14
- package/src/plugins/auto-update-node.ts +10 -6
- package/src/plugins/builtin/account-management/form-components.tsx +1 -1
- package/src/plugins/builtin/account-management/index.tsx +2 -3
- package/src/plugins/builtin/account-management/model.ts +1 -1
- package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
- package/src/plugins/builtin/alerts/events.ts +10 -2
- package/src/plugins/builtin/alerts/index.tsx +5 -5
- package/src/plugins/builtin/alerts/research-builder.ts +5 -1
- package/src/plugins/builtin/alerts/research-rules.ts +18 -0
- package/src/plugins/builtin/alerts/targets.ts +4 -1
- package/src/plugins/builtin/attention/headless.ts +1 -1
- package/src/plugins/builtin/attention/index.tsx +2 -4
- package/src/plugins/builtin/attention/listing.ts +1 -1
- package/src/plugins/builtin/attention/series.ts +1 -1
- package/src/plugins/builtin/awards/index.tsx +1 -2
- package/src/plugins/builtin/backtest/client.ts +1 -0
- package/src/plugins/builtin/backtest/index.tsx +1 -2
- package/src/plugins/builtin/backtest/rules.ts +3 -1
- package/src/plugins/builtin/backtest/window.ts +8 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
- package/src/plugins/builtin/broker-manager/index.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +5 -0
- package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
- package/src/plugins/builtin/buildout/company/id.ts +2 -0
- package/src/plugins/builtin/buildout/company/model.ts +247 -0
- package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
- package/src/plugins/builtin/buildout/facilities.ts +309 -0
- package/src/plugins/builtin/buildout/format.ts +9 -0
- package/src/plugins/builtin/buildout/index.tsx +51 -5
- package/src/plugins/builtin/buildout/model/index.ts +62 -10
- package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
- package/src/plugins/builtin/buildout/model/types.ts +11 -2
- package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
- package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
- package/src/plugins/builtin/buildout/table-model.ts +26 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
- package/src/plugins/builtin/cds/headless.ts +10 -2
- package/src/plugins/builtin/cds/model.ts +7 -3
- package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
- package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
- package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
- package/src/plugins/builtin/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
- package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
- package/src/plugins/builtin/chart-composer/headless.ts +88 -21
- package/src/plugins/builtin/chart-composer/index.tsx +2 -3
- package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
- package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/controller/index.ts +1 -1
- package/src/plugins/builtin/chat/controller/messages.ts +15 -4
- package/src/plugins/builtin/chat/ghost-user.ts +8 -0
- package/src/plugins/builtin/chat/index.tsx +5 -5
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/index.tsx +1 -2
- package/src/plugins/builtin/cloud/plugin.tsx +6 -4
- package/src/plugins/builtin/cloud/team/module.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
- package/src/plugins/builtin/comment-letters/index.tsx +1 -2
- package/src/plugins/builtin/commodity-balances/client.ts +4 -1
- package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
- package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
- package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
- package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
- package/src/plugins/builtin/congress-trades/headless.ts +2 -0
- package/src/plugins/builtin/congress-trades/model.ts +92 -64
- package/src/plugins/builtin/congress-trades/table.tsx +14 -2
- package/src/plugins/builtin/connections/index.ts +2 -3
- package/src/plugins/builtin/cot/client.ts +28 -2
- package/src/plugins/builtin/cot/headless.ts +4 -1
- package/src/plugins/builtin/cot/index.tsx +5 -4
- package/src/plugins/builtin/cot/model.ts +58 -7
- package/src/plugins/builtin/cot/pane.tsx +6 -2
- package/src/plugins/builtin/cpi/client.ts +6 -5
- package/src/plugins/builtin/cpi/index.tsx +1 -2
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/client.ts +7 -2
- package/src/plugins/builtin/credit-boards/headless.ts +25 -12
- package/src/plugins/builtin/credit-boards/model.ts +29 -0
- package/src/plugins/builtin/credit-boards/series.ts +3 -4
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/credit-conditions/client.ts +22 -14
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- package/src/plugins/builtin/crypto-board/client.ts +5 -5
- package/src/plugins/builtin/crypto-board/headless.ts +4 -3
- package/src/plugins/builtin/crypto-board/index.tsx +4 -2
- package/src/plugins/builtin/crypto-board/series.ts +6 -4
- package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
- package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
- package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
- package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
- package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
- package/src/plugins/builtin/debt-maturities/model.ts +17 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
- package/src/plugins/builtin/debt-maturities/series.ts +3 -3
- package/src/plugins/builtin/debug/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/client.ts +32 -2
- package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
- package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
- package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
- package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
- package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
- package/src/plugins/builtin/dividend-yield/view.ts +21 -0
- package/src/plugins/builtin/docs/index.tsx +2 -3
- package/src/plugins/builtin/doe/client.ts +6 -5
- package/src/plugins/builtin/doe/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/doe/prices.ts +2 -3
- package/src/plugins/builtin/doe/series.ts +4 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
- package/src/plugins/builtin/earnings-calls/data.ts +19 -11
- package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
- package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
- package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
- package/src/plugins/builtin/econ/calendar-model.ts +29 -6
- package/src/plugins/builtin/econ/detail-view.tsx +16 -2
- package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +1 -0
- package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
- package/src/plugins/builtin/equity-screener/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/model.ts +28 -4
- package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
- package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
- package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
- package/src/plugins/builtin/executives/data.ts +6 -1
- package/src/plugins/builtin/executives/pane.tsx +9 -21
- package/src/plugins/builtin/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/cache.ts +4 -0
- package/src/plugins/builtin/fear-greed/cli.ts +44 -0
- package/src/plugins/builtin/fear-greed/index.tsx +5 -53
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
- package/src/plugins/builtin/fear-greed/series.ts +6 -4
- package/src/plugins/builtin/fixed-income/index.tsx +1 -2
- package/src/plugins/builtin/futures/board-reads.ts +25 -0
- package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
- package/src/plugins/builtin/futures/index.tsx +15 -0
- package/src/plugins/builtin/futures/model.ts +14 -0
- package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
- package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
- package/src/plugins/builtin/futures-curve/headless.ts +6 -1
- package/src/plugins/builtin/futures-curve/index.tsx +1 -2
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +51 -12
- package/src/plugins/builtin/gpu/client.ts +19 -3
- package/src/plugins/builtin/gpu/equities.tsx +5 -2
- package/src/plugins/builtin/gpu/headless.ts +14 -2
- package/src/plugins/builtin/gpu/index.tsx +1 -2
- package/src/plugins/builtin/gpu/model.ts +20 -2
- package/src/plugins/builtin/gpu/pane.tsx +1 -1
- package/src/plugins/builtin/gpu/series.ts +2 -3
- package/src/plugins/builtin/help/index.tsx +1 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
- package/src/plugins/builtin/holders/index-holders.ts +24 -12
- package/src/plugins/builtin/holders/index.tsx +2 -3
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
- package/src/plugins/builtin/iv-history/index.tsx +1 -2
- package/src/plugins/builtin/jodi/client.ts +4 -1
- package/src/plugins/builtin/jodi/index.tsx +1 -2
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
- package/src/plugins/builtin/layout-manager/index.tsx +9 -8
- package/src/plugins/builtin/macro-day/pane.tsx +4 -2
- package/src/plugins/builtin/market-halts/index.tsx +1 -2
- package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
- package/src/plugins/builtin/market-movers/client.ts +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +16 -5
- package/src/plugins/builtin/market-movers/screener.ts +23 -6
- package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
- package/src/plugins/builtin/market-valuation/index.tsx +1 -2
- package/src/plugins/builtin/members/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/pane.tsx +2 -1
- package/src/plugins/builtin/money-markets/series.ts +3 -5
- package/src/plugins/builtin/news/headless.ts +7 -0
- package/src/plugins/builtin/news/wire/index.tsx +1 -1
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
- package/src/plugins/builtin/notes/index.tsx +3 -6
- package/src/plugins/builtin/openfda/index.tsx +1 -2
- package/src/plugins/builtin/options/view.tsx +6 -5
- package/src/plugins/builtin/options-calculator/index.tsx +1 -2
- package/src/plugins/builtin/options-positioning/index.tsx +1 -2
- package/src/plugins/builtin/options-scenario/index.tsx +2 -4
- package/src/plugins/builtin/perps/index.tsx +1 -2
- package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
- package/src/plugins/builtin/plugin-module.ts +16 -2
- package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
- package/src/plugins/builtin/power/gas-client.ts +2 -1
- package/src/plugins/builtin/power/index.tsx +1 -2
- package/src/plugins/builtin/power/prices-client.ts +3 -2
- package/src/plugins/builtin/rate-path/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/model.ts +16 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
- package/src/plugins/builtin/research/analyst-headless.ts +33 -6
- package/src/plugins/builtin/research/analyst-model.ts +37 -2
- package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
- package/src/plugins/builtin/research/index.tsx +5 -8
- package/src/plugins/builtin/research-search/index.tsx +1 -2
- package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
- package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
- package/src/plugins/builtin/risk-factors/data.ts +8 -4
- package/src/plugins/builtin/risk-factors/index.tsx +3 -1
- package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
- package/src/plugins/builtin/roadmap/index.tsx +2 -3
- package/src/plugins/builtin/roadmap/pane.tsx +49 -2
- package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
- package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
- package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
- package/src/plugins/builtin/scanner/lock-note.ts +26 -3
- package/src/plugins/builtin/sec/filing-content.ts +38 -6
- package/src/plugins/builtin/sec/headless.ts +5 -1
- package/src/plugins/builtin/sec/index.tsx +4 -2
- package/src/plugins/builtin/sec/model.ts +2 -0
- package/src/plugins/builtin/sectors/index.tsx +11 -5
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
- package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
- package/src/plugins/builtin/shared/forming-bars.ts +29 -0
- package/src/plugins/builtin/shared/fred-points.ts +6 -1
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- package/src/plugins/builtin/shared/series-warning.ts +13 -4
- package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
- package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/series.ts +2 -3
- package/src/plugins/builtin/social-mentions/client.ts +15 -10
- package/src/plugins/builtin/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- package/src/plugins/builtin/social-mentions/series.ts +2 -3
- package/src/plugins/builtin/supply-chain/client.ts +22 -1
- package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
- package/src/plugins/builtin/supply-chain/index.tsx +1 -2
- package/src/plugins/builtin/thirteenf/api.ts +127 -29
- package/src/plugins/builtin/thirteenf/data.ts +165 -35
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
- package/src/plugins/builtin/thirteenf/types.ts +14 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
- package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
- package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
- package/src/plugins/builtin/time-sales/index.tsx +1 -2
- package/src/plugins/builtin/time-sales/pane.tsx +3 -1
- package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
- package/src/plugins/builtin/trace-bonds/model.ts +44 -10
- package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
- package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
- package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
- package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
- package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
- package/src/plugins/builtin/vol-surface/client.ts +18 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -2
- package/src/plugins/builtin/volatility/client.ts +43 -16
- package/src/plugins/builtin/volatility/headless.ts +5 -2
- package/src/plugins/builtin/volatility/index.tsx +1 -2
- package/src/plugins/builtin/volatility/model.ts +17 -6
- package/src/plugins/builtin/volatility/pane.tsx +6 -2
- package/src/plugins/builtin/volatility/series.ts +2 -1
- package/src/plugins/builtin/world-indices/footer.ts +6 -1
- package/src/plugins/builtin/world-venue-map/client.ts +10 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -2
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
- package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
- package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
- package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
- package/src/plugins/builtin/yield-curve/client.ts +4 -6
- package/src/plugins/builtin/yield-curve/curves.ts +16 -2
- package/src/plugins/builtin/yield-curve/headless.ts +3 -3
- package/src/plugins/bundle.ts +52 -2
- package/src/plugins/catalog-browser.ts +2 -2
- package/src/plugins/catalog-cli.ts +20 -0
- package/src/plugins/host-link.ts +13 -1
- package/src/plugins/installer.ts +64 -42
- package/src/plugins/loader.ts +43 -11
- package/src/plugins/peer-imports.ts +201 -0
- package/src/plugins/peer-retry.ts +26 -29
- package/src/plugins/plugin-process.ts +128 -0
- package/src/plugins/redirection-guard.ts +45 -0
- package/src/remote/endpoint.ts +16 -3
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/http-proxy.ts +7 -123
- package/src/renderers/dom/desktop/icons.tsx +5 -2
- package/src/renderers/dom/host/image-surface.tsx +46 -21
- package/src/renderers/dom/styles.css +7 -0
- package/src/renderers/dom/toast-host.tsx +2 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
- package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
- package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
- package/src/renderers/electrobun/bun/window/attention.ts +54 -0
- package/src/renderers/electrobun/bun/window/create.ts +6 -2
- package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
- package/src/renderers/electrobun/view/main.tsx +7 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/renderers/electrobun/view/ui-host.tsx +9 -2
- package/src/renderers/opentui/browser-open.ts +10 -4
- package/src/renderers/opentui/image/surface.tsx +1 -1
- package/src/renderers/opentui/start.tsx +12 -6
- package/src/renderers/opentui/toast-host.tsx +2 -1
- package/src/series/registry.ts +18 -0
- package/src/series/resolve.ts +2 -1
- package/src/series/standalone.ts +10 -1
- package/src/series/types.ts +12 -0
- package/src/series/validate.ts +11 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
- package/src/sources/gloomberb-cloud/index.ts +28 -5
- package/src/sources/gloomberb-cloud/news.ts +24 -3
- package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
- package/src/sources/provider-errors.ts +26 -3
- package/src/sources/provider-router/history.ts +5 -0
- package/src/sources/provider-router/index.ts +13 -0
- package/src/sync/core-contributors.ts +32 -0
- package/src/telemetry/attention-counts.ts +64 -6
- package/src/telemetry/attention-setting.ts +33 -0
- package/src/telemetry/crash-reports-node.ts +3 -1
- package/src/telemetry/stall-clocks.ts +93 -0
- package/src/telemetry/stall-reports-node.ts +25 -9
- package/src/telemetry/stall-reports.ts +218 -13
- package/src/test-support/outage.ts +8 -0
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name-listing.ts +59 -29
- package/src/tickers/company-name.ts +84 -1
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +104 -8
- package/src/tickers/search/index.ts +5 -2
- package/src/tickers/search/types.ts +6 -1
- package/src/time-series/bar-aggregation.ts +7 -1
- package/src/time-series/base-series.ts +3 -3
- package/src/time-series/chart-bounds.ts +1 -1
- package/src/time-series/chart-data.ts +5 -2
- package/src/time-series/chart-seed.ts +1 -1
- package/src/time-series/coin-session-close.ts +211 -0
- package/src/time-series/live-bars.ts +13 -2
- package/src/time-series/live-quotes.ts +1 -1
- package/src/time-series/market.ts +5 -3
- package/src/time-series/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +75 -8
- package/src/time-series/resolve.ts +32 -4
- package/src/time-series/series-presentation.ts +1 -1
- package/src/time-series/studies/formula.ts +205 -0
- package/src/time-series/studies/indicators.ts +299 -0
- package/src/time-series/studies/leverage.ts +75 -0
- package/src/time-series/studies/pair.ts +138 -0
- package/src/time-series/studies/resolve.ts +333 -0
- package/src/time-series/studies/shared.ts +141 -0
- package/src/time-series/studies/units.ts +243 -0
- package/src/time-series/studies/vwap.ts +274 -0
- package/src/time-series/types.ts +7 -0
- package/src/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +40 -3
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +17 -1
- package/src/ui/toast.tsx +26 -1
- package/src/utils/ascii-text.ts +140 -0
- package/src/utils/cli-output.ts +91 -18
- package/src/utils/concurrency.ts +24 -0
- package/src/utils/exchanges.ts +6 -0
- package/src/utils/external-url.ts +20 -7
- package/src/utils/financial-statements.ts +73 -16
- package/src/utils/fundamentals.ts +17 -5
- package/src/utils/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/lease-liabilities.ts +54 -0
- package/src/utils/main-thread-stall.ts +122 -5
- package/src/utils/perf-marks.ts +58 -12
- package/src/utils/price-history-integrity.ts +47 -1
- package/src/utils/private-files.ts +141 -0
- package/src/utils/public-address.ts +99 -0
- package/src/account/upgrade-personalization.ts +0 -57
- package/src/account/upgrade-tickers.ts +0 -124
- package/src/api-client/upgrade-experiment.ts +0 -91
- package/src/plugins/builtin/cloud/browser.tsx +0 -3
- package/src/time-series/studies.ts +0 -1628
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
type CloudProxyStatementListPayload,
|
|
4
4
|
type CloudProxyStatementPayload,
|
|
5
5
|
} from "../../../api-client";
|
|
6
|
-
import { ApiRequestError, isPermanentClientError } from "../../../api-client/errors";
|
|
6
|
+
import { ApiRequestError, isPermanentClientError, isTransientFailure } from "../../../api-client/errors";
|
|
7
7
|
import type { IssuerListingParams } from "../../../api-client/paths";
|
|
8
8
|
import type { PluginPersistence } from "../../../types/plugin";
|
|
9
9
|
import { secListingKey } from "../../../utils/sec";
|
|
@@ -46,6 +46,8 @@ interface ProxyResult<T> {
|
|
|
46
46
|
/** Null is an explicit 404, not a transient failure or access denial. */
|
|
47
47
|
data: T | null;
|
|
48
48
|
fetchedAt: number | null;
|
|
49
|
+
/** Its refresh failed and the copy saved at this time (ms) answered instead. */
|
|
50
|
+
savedAt?: number;
|
|
49
51
|
refreshError?: string;
|
|
50
52
|
}
|
|
51
53
|
type ActiveRequest<T> = { store: PluginPersistence | null; promise: Promise<ProxyResult<T>> };
|
|
@@ -105,11 +107,14 @@ function loadCached<T>(
|
|
|
105
107
|
}
|
|
106
108
|
throw error;
|
|
107
109
|
}
|
|
110
|
+
// A saved copy answers only for a failure asking again may get past, not for a reply that fails validation.
|
|
111
|
+
if (!isTransientFailure(error)) throw error;
|
|
108
112
|
const fallback = store?.getResource<T>(kind, key, { ...options, allowExpired: true });
|
|
109
113
|
if (!fallback || tierOf(fallback.value) !== tier) throw error;
|
|
110
114
|
return {
|
|
111
115
|
data: fallback.value,
|
|
112
116
|
fetchedAt: fallback.fetchedAt,
|
|
117
|
+
savedAt: fallback.fetchedAt,
|
|
113
118
|
refreshError: (error instanceof Error ? error.message : String(error)).trim() || "Request failed",
|
|
114
119
|
};
|
|
115
120
|
}).finally(() => { if (active.get(activeKey) === request) active.delete(activeKey); });
|
|
@@ -8,12 +8,12 @@ import {
|
|
|
8
8
|
DataTableView,
|
|
9
9
|
EmptyState, FigureList, PaneStatusBody, Prose, QueryBar, READING_WIDTH, SectionHeading,
|
|
10
10
|
usePaneFooter,
|
|
11
|
-
usePaneNoticeFooter,
|
|
12
11
|
type DataTableCell,
|
|
13
12
|
type DataTableColumn,
|
|
14
13
|
type PaneFooterSegment,
|
|
15
14
|
type PaneHint,
|
|
16
15
|
} from "../../../components";
|
|
16
|
+
import { savedCopyFooterInfo } from "../../../components/layout/pane/footer";
|
|
17
17
|
import { useAsyncResource } from "../../../react/async-resource";
|
|
18
18
|
import { usePaneStateValue } from "../../../state/app/context";
|
|
19
19
|
import { colors, getChartIndicatorColor } from "../../../theme/colors";
|
|
@@ -45,13 +45,6 @@ import {
|
|
|
45
45
|
|
|
46
46
|
export const EXECUTIVES_PANE_ID = "executives";
|
|
47
47
|
|
|
48
|
-
function retainedDataNotice(subject: string, error: string, fetchedAt: number | null): string {
|
|
49
|
-
const retrieved = new Date(fetchedAt ?? Number.NaN);
|
|
50
|
-
const age = Number.isFinite(retrieved.getTime())
|
|
51
|
-
? `retrieved ${retrieved.toISOString()}` : "with unavailable retrieval time";
|
|
52
|
-
return `${subject} refresh failed: ${error}. Retained data ${age}.`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
48
|
const PAY_PARTS: Array<{ key: keyof CloudExecutiveRowPayload; label: string }> =
|
|
56
49
|
[
|
|
57
50
|
{ key: "salary", label: "Salary" },
|
|
@@ -301,18 +294,10 @@ function ExecutiveResearch({ ticker, listing, tier, focused, width, nested, guar
|
|
|
301
294
|
// The free preview: each executive's total pay; the breakdown and earlier years are Pro.
|
|
302
295
|
const preview = statement?.access === "preview" || list.data?.data?.access === "preview";
|
|
303
296
|
const loading = list.loading || detail.loading;
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
notices: [
|
|
309
|
-
list.data?.data && listError
|
|
310
|
-
? retainedDataNotice("Proxy list", listError, list.data.fetchedAt) : null,
|
|
311
|
-
detail.data?.data && statementError
|
|
312
|
-
? retainedDataNotice(`${year} proxy`, statementError, detail.data.fetchedAt) : null,
|
|
313
|
-
].filter((notice): notice is string => notice !== null),
|
|
314
|
-
focused,
|
|
315
|
-
});
|
|
297
|
+
// A copy saved earlier answering a failed refresh says when it was saved, in the footer every pane uses;
|
|
298
|
+
// a failed refresh with only what is on screen says why. Without anything shown, the body says it.
|
|
299
|
+
const listError = list.data?.data ? list.error ?? (list.data.savedAt == null ? list.data.refreshError : null) ?? null : null;
|
|
300
|
+
const statementError = detail.data?.data ? detail.error ?? (detail.data.savedAt == null ? detail.data.refreshError : null) ?? null : null;
|
|
316
301
|
const refresh = useCallback(() => {
|
|
317
302
|
void list.reload();
|
|
318
303
|
if (year !== null) void detail.reload();
|
|
@@ -338,6 +323,9 @@ function ExecutiveResearch({ ticker, listing, tier, focused, width, nested, guar
|
|
|
338
323
|
const info: PaneFooterSegment[] = [];
|
|
339
324
|
if (loading)
|
|
340
325
|
info.push({ id: "loading", parts: [{ text: "loading", tone: "muted" }] });
|
|
326
|
+
if (listError) info.push({ id: "list-error", parts: [{ text: `Proxy list: ${listError}`, tone: "warning" }] });
|
|
327
|
+
if (statementError) info.push({ id: "statement-error", parts: [{ text: `${year} proxy: ${statementError}`, tone: "warning" }] });
|
|
328
|
+
info.push(...savedCopyFooterInfo([list.data?.data ? list.data.savedAt : null, statement ? detail.data?.savedAt : null]));
|
|
341
329
|
if (statement) {
|
|
342
330
|
info.push({
|
|
343
331
|
id: "filed",
|
|
@@ -351,7 +339,7 @@ function ExecutiveResearch({ ticker, listing, tier, focused, width, nested, guar
|
|
|
351
339
|
if (yearHint) hints.push(yearHint);
|
|
352
340
|
if (preview) hints.push({ id: "upgrade", key: CLOUD_PLAN_KEY, label: "upgrade", title: "Upgrade to Pro", onPress: openUpgrade });
|
|
353
341
|
return { info, hints };
|
|
354
|
-
}, [loading, statement, openFiling, yearHint, preview, openUpgrade]);
|
|
342
|
+
}, [loading, listError, statementError, year, list.data, detail.data, statement, openFiling, yearHint, preview, openUpgrade]);
|
|
355
343
|
|
|
356
344
|
const figures = useMemo(
|
|
357
345
|
() => (statement ? figuresOf(statement) : []),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExposureHolding } from "../../../api-client/exposure";
|
|
2
2
|
import type { DataProvider } from "../../../types/data-provider";
|
|
3
|
+
import type { Quote } from "../../../types/financials";
|
|
3
4
|
import type { Portfolio, TickerRecord } from "../../../types/ticker";
|
|
4
5
|
import { publicTickerKey } from "../../../utils/exchanges";
|
|
5
6
|
import { resolveCurrencyUnit } from "../../../utils/currency-units";
|
|
@@ -7,8 +8,25 @@ import { convertCurrency } from "../../../utils/format";
|
|
|
7
8
|
import { logFailure } from "../../../utils/log-failure";
|
|
8
9
|
import { getPortfolioPositionMetrics, getPortfolioQuoteDisplay, resolvePortfolioMarketValue } from "../../../portfolio/position-metrics";
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Every position's quote: one batch where the provider has one, else one
|
|
13
|
+
* request per position, all at once. A position without a quote is null.
|
|
14
|
+
*/
|
|
15
|
+
async function positionQuotes(positions: TickerRecord[], market: Pick<DataProvider, "getQuote" | "getQuotesBatch">): Promise<Array<Quote | null>> {
|
|
16
|
+
const failed = logFailure("exposure.holdings", "Load portfolio holding quote failed", null);
|
|
17
|
+
if (market.getQuotesBatch) {
|
|
18
|
+
const targets = positions.map((ticker) => ({ symbol: ticker.metadata.ticker, exchange: ticker.metadata.exchange, surface: "portfolio" as const }));
|
|
19
|
+
const results = await market.getQuotesBatch(targets).catch((error: unknown) => {
|
|
20
|
+
failed(error);
|
|
21
|
+
return [];
|
|
22
|
+
});
|
|
23
|
+
return positions.map((_, index) => results[index]?.quote ?? null);
|
|
24
|
+
}
|
|
25
|
+
return Promise.all(positions.map((ticker) => market.getQuote(ticker.metadata.ticker, ticker.metadata.exchange).catch(failed)));
|
|
26
|
+
}
|
|
27
|
+
|
|
10
28
|
/** Explicit NAV preserves cash, shorts and leverage; net holdings are never used as NAV. */
|
|
11
|
-
export async function portfolioHoldings(portfolio: Portfolio, tickers: TickerRecord[], nav: number, market: Pick<DataProvider, "getQuote" | "getExchangeRate">): Promise<ExposureHolding[]> {
|
|
29
|
+
export async function portfolioHoldings(portfolio: Portfolio, tickers: TickerRecord[], nav: number, market: Pick<DataProvider, "getQuote" | "getQuotesBatch" | "getExchangeRate">): Promise<ExposureHolding[]> {
|
|
12
30
|
if (!Number.isFinite(nav) || nav <= 0) throw new Error(`Enter positive NAV in ${portfolio.currency} for ${portfolio.name}.`);
|
|
13
31
|
const positions = tickers.filter(t => t.metadata.positions.some(p => p.portfolio === portfolio.id && p.shares !== 0));
|
|
14
32
|
if (!positions.length) throw new Error("The selected portfolio has no positions.");
|
|
@@ -21,25 +39,29 @@ export async function portfolioHoldings(portfolio: Portfolio, tickers: TickerRec
|
|
|
21
39
|
t.metadata.positions.filter(p => p.portfolio === portfolio.id).forEach(p => { if (p.currency) currencies.add(p.currency); });
|
|
22
40
|
}
|
|
23
41
|
const rates = new Map<string, number>([["USD", 1]]);
|
|
24
|
-
|
|
42
|
+
// One request per major currency, however many of its units (GBP, GBp) ask at once.
|
|
43
|
+
const majorRates = new Map<string, Promise<number>>([["USD", Promise.resolve(1)]]);
|
|
25
44
|
const ensureRate = async (rawCurrency: string) => {
|
|
26
45
|
const unit = resolveCurrencyUnit(rawCurrency);
|
|
27
46
|
if (!unit.currency) throw new Error("Portfolio currency is missing.");
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
47
|
+
const major = unit.currency;
|
|
48
|
+
let pending = majorRates.get(major);
|
|
49
|
+
if (!pending) {
|
|
50
|
+
pending = market.getExchangeRate(major).then((rate) => {
|
|
51
|
+
if (!Number.isFinite(rate) || rate <= 0) throw new Error(`FX unavailable for ${major}; weights were not estimated.`);
|
|
52
|
+
return rate;
|
|
53
|
+
});
|
|
54
|
+
majorRates.set(major, pending);
|
|
33
55
|
}
|
|
34
|
-
rates.set(rawCurrency,
|
|
56
|
+
rates.set(rawCurrency, (await pending) / unit.divisor);
|
|
35
57
|
};
|
|
36
|
-
|
|
58
|
+
const [quotes] = await Promise.all([positionQuotes(positions, market), Promise.all([...currencies].map(ensureRate))]);
|
|
59
|
+
// A quote in a currency the positions did not name.
|
|
60
|
+
await Promise.all([...new Set(quotes.flatMap((quote) => quote?.currency && !rates.has(quote.currency) ? [quote.currency] : []))].map(ensureRate));
|
|
37
61
|
const holdings: ExposureHolding[] = [];
|
|
38
|
-
for (const ticker of positions) {
|
|
39
|
-
const quote =
|
|
40
|
-
.catch(logFailure("exposure.holdings", "Load portfolio holding quote failed", null));
|
|
62
|
+
for (const [index, ticker] of positions.entries()) {
|
|
63
|
+
const quote = quotes[index] ?? null;
|
|
41
64
|
const currency = quote?.currency || ticker.metadata.currency;
|
|
42
|
-
if (!rates.has(currency)) await ensureRate(currency);
|
|
43
65
|
const metrics = getPortfolioPositionMetrics(ticker, portfolio.id, currency, {
|
|
44
66
|
currency: portfolio.currency, convert: (value, from) => convertCurrency(value, from, portfolio.currency, rates),
|
|
45
67
|
}, quote);
|
|
@@ -2,7 +2,6 @@ import type { PaneTemplateContext, ShortcutArgError } from "../../../types/plugi
|
|
|
2
2
|
import { findNamedCollection, unknownCollectionArgError } from "../../../portfolio/collection-name";
|
|
3
3
|
import type { PluginModule } from "../plugin-module";
|
|
4
4
|
import { exposureHeadless } from "./headless";
|
|
5
|
-
import { ExposurePane } from "./pane";
|
|
6
5
|
|
|
7
6
|
const SOURCE = /^(PORT|WATCH):(.+)$/i;
|
|
8
7
|
|
|
@@ -29,7 +28,7 @@ function unknownSourceArg(arg: string, context: PaneTemplateContext): ShortcutAr
|
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
export const exposureModule: PluginModule = {
|
|
32
|
-
panes: [{ id: "exposure", name: "Exposure", icon: "E", component: ExposurePane, defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 138, height: 31 }, tableExport: true, headless: exposureHeadless,
|
|
31
|
+
panes: [{ id: "exposure", name: "Exposure", icon: "E", get component(): typeof import("./pane").ExposurePane { const { ExposurePane } = require("./pane"); return ExposurePane; }, defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 138, height: 31 }, tableExport: true, headless: exposureHeadless,
|
|
33
32
|
personalData: "holdings",
|
|
34
33
|
portableShare: { private: { params: true, settings: true, state: true } } }],
|
|
35
34
|
paneTemplates: [{ id: "exposure-pane", paneId: "exposure", label: "Exposure", description: "Pro scenario exposures across holdings, geographic revenue and supply chains, with source evidence and portfolio concentrations.",
|
|
@@ -35,6 +35,10 @@ export type FearGreedCacheEntry = {
|
|
|
35
35
|
export interface FearGreedLoadResult extends FearGreedCacheEntry {
|
|
36
36
|
/** Set when the network failed and cached data was served instead. */
|
|
37
37
|
refreshError?: string;
|
|
38
|
+
/** `stale-fallback` when a saved copy answered a failed refresh. */
|
|
39
|
+
source?: "cache" | "network" | "stale-fallback";
|
|
40
|
+
/** The failure a `stale-fallback` copy answered. */
|
|
41
|
+
error?: unknown;
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
const cache = createPluginCache<FearGreedData, PersistedFearGreedData>({
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CLI_COMMAND_GROUPS } from "../../../cli/help";
|
|
2
|
+
import { rowsFreshness } from "../../../cli/freshness";
|
|
3
|
+
import { humanizeCliKey } from "../../../cli/result";
|
|
4
|
+
import type { CliCommandDef } from "../../../types/plugin";
|
|
5
|
+
import { loadFearGreed } from "./cache";
|
|
6
|
+
|
|
7
|
+
const SCORE_KEYS = new Set(["score", "previousClose", "previousWeek", "previousMonth", "previousYear"]);
|
|
8
|
+
/** A score to a tenth of a point (17.9); a missing one leaves its cell empty, as before. */
|
|
9
|
+
const scoreTenths = (value: unknown) => typeof value === "number" && Number.isFinite(value) ? value.toFixed(1) : "";
|
|
10
|
+
|
|
11
|
+
// Without the pane cache attached, this fetches a fresh reading for the command.
|
|
12
|
+
export const fearGreedCliCommand: CliCommandDef = {
|
|
13
|
+
name: "fear-greed",
|
|
14
|
+
description: "Fetch CNN Fear & Greed gauge data",
|
|
15
|
+
help: { group: CLI_COMMAND_GROUPS.markets },
|
|
16
|
+
async execute(_args, ctx) {
|
|
17
|
+
const { data, stale } = await loadFearGreed(ctx.cliOptions.refresh);
|
|
18
|
+
const rows = [{
|
|
19
|
+
score: data.overall.score,
|
|
20
|
+
rating: data.overall.rating,
|
|
21
|
+
updatedAt: data.overall.updatedAt?.toISOString() ?? "",
|
|
22
|
+
previousClose: data.overall.previousClose,
|
|
23
|
+
previousWeek: data.overall.previousWeek,
|
|
24
|
+
previousMonth: data.overall.previousMonth,
|
|
25
|
+
previousYear: data.overall.previousYear,
|
|
26
|
+
}];
|
|
27
|
+
ctx.printResult({
|
|
28
|
+
data: rows,
|
|
29
|
+
// Exports cite CNN's reading as `fn FNG` does; the text stays the row alone.
|
|
30
|
+
exportFreshness: rowsFreshness(rows, { source: "CNN", status: stale ? "stale" : "not-a-feed", basis: "published index" }),
|
|
31
|
+
metadata: {
|
|
32
|
+
indicators: data.indicators.map((indicator) => ({
|
|
33
|
+
id: indicator.definition.id,
|
|
34
|
+
score: indicator.score,
|
|
35
|
+
rating: indicator.rating,
|
|
36
|
+
})),
|
|
37
|
+
},
|
|
38
|
+
}, {
|
|
39
|
+
// Scores read to a tenth of a point on the text table; exports keep every digit.
|
|
40
|
+
textColumns: Object.keys(rows[0]!).map((key) => ({ key, header: humanizeCliKey(key),
|
|
41
|
+
...(SCORE_KEYS.has(key) ? { align: "right" as const, format: scoreTenths } : {}) })),
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -1,26 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { humanizeCliKey } from "../../../cli/result";
|
|
1
|
+
import { fearGreedPluginMeta } from "../builtin-plugin-meta";
|
|
2
|
+
import { fearGreedCliCommand } from "./cli";
|
|
4
3
|
import type { GloomPlugin } from "../../../types/plugin";
|
|
5
4
|
import {
|
|
6
5
|
attachFearGreedPersistence,
|
|
7
|
-
loadFearGreed,
|
|
8
6
|
resetFearGreedPersistence,
|
|
9
7
|
} from "./cache";
|
|
10
|
-
import { FearGreedPane } from "./pane";
|
|
11
8
|
import { fearGreedHeadless } from "./headless";
|
|
12
9
|
import { FEAR_GREED_SERIES } from "./series";
|
|
13
10
|
|
|
14
|
-
const SCORE_KEYS = new Set(["score", "previousClose", "previousWeek", "previousMonth", "previousYear"]);
|
|
15
|
-
/** A score to a tenth of a point (17.9); a missing one leaves its cell empty, as before. */
|
|
16
|
-
const scoreTenths = (value: unknown) => typeof value === "number" && Number.isFinite(value) ? value.toFixed(1) : "";
|
|
17
|
-
|
|
18
11
|
export const fearGreedPlugin: GloomPlugin = {
|
|
19
|
-
|
|
20
|
-
name: "Fear & Greed",
|
|
21
|
-
version: "1.0.0",
|
|
22
|
-
description: "CNN Fear & Greed index with the seven indicator charts",
|
|
23
|
-
toggleable: true,
|
|
12
|
+
...fearGreedPluginMeta,
|
|
24
13
|
|
|
25
14
|
// One JSON endpoint over HTTPS, so every renderer. CNN sends no CORS
|
|
26
15
|
// headers and wants a Referer, which is why the host is declared: the web
|
|
@@ -44,7 +33,7 @@ export const fearGreedPlugin: GloomPlugin = {
|
|
|
44
33
|
id: "fear-greed",
|
|
45
34
|
name: "Fear & Greed",
|
|
46
35
|
icon: "G",
|
|
47
|
-
component: FearGreedPane,
|
|
36
|
+
get component(): typeof import("./pane").FearGreedPane { const { FearGreedPane } = require("./pane"); return FearGreedPane; },
|
|
48
37
|
defaultPosition: "right",
|
|
49
38
|
defaultMode: "floating",
|
|
50
39
|
defaultFloatingSize: { width: 110, height: 36 },
|
|
@@ -63,42 +52,5 @@ export const fearGreedPlugin: GloomPlugin = {
|
|
|
63
52
|
},
|
|
64
53
|
],
|
|
65
54
|
|
|
66
|
-
|
|
67
|
-
// without the pane cache attached when invoked from the CLI, so it always
|
|
68
|
-
// fetches, which is what a CLI call wants anyway.
|
|
69
|
-
cliCommands: [
|
|
70
|
-
{
|
|
71
|
-
name: "fear-greed",
|
|
72
|
-
description: "Fetch CNN Fear & Greed gauge data",
|
|
73
|
-
help: { group: CLI_COMMAND_GROUPS.markets },
|
|
74
|
-
async execute(_args, ctx) {
|
|
75
|
-
const { data, stale } = await loadFearGreed(ctx.cliOptions.refresh);
|
|
76
|
-
const rows = [{
|
|
77
|
-
score: data.overall.score,
|
|
78
|
-
rating: data.overall.rating,
|
|
79
|
-
updatedAt: data.overall.updatedAt?.toISOString() ?? "",
|
|
80
|
-
previousClose: data.overall.previousClose,
|
|
81
|
-
previousWeek: data.overall.previousWeek,
|
|
82
|
-
previousMonth: data.overall.previousMonth,
|
|
83
|
-
previousYear: data.overall.previousYear,
|
|
84
|
-
}];
|
|
85
|
-
ctx.printResult({
|
|
86
|
-
data: rows,
|
|
87
|
-
// Exports cite CNN's reading as `fn FNG` does; the text stays the row alone.
|
|
88
|
-
exportFreshness: rowsFreshness(rows, { source: "CNN", status: stale ? "stale" : "not-a-feed", basis: "published index" }),
|
|
89
|
-
metadata: {
|
|
90
|
-
indicators: data.indicators.map((indicator) => ({
|
|
91
|
-
id: indicator.definition.id,
|
|
92
|
-
score: indicator.score,
|
|
93
|
-
rating: indicator.rating,
|
|
94
|
-
})),
|
|
95
|
-
},
|
|
96
|
-
}, {
|
|
97
|
-
// Scores read to a tenth of a point on the text table; exports keep every digit.
|
|
98
|
-
textColumns: Object.keys(rows[0]!).map((key) => ({ key, header: humanizeCliKey(key),
|
|
99
|
-
...(SCORE_KEYS.has(key) ? { align: "right" as const, format: scoreTenths } : {}) })),
|
|
100
|
-
});
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
],
|
|
55
|
+
cliCommands: [fearGreedCliCommand],
|
|
104
56
|
};
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
usePaneStatusFooter,
|
|
9
9
|
type StatItem,
|
|
10
10
|
} from "../../../components";
|
|
11
|
+
import { ageFooterSegment } from "../../../components/layout/pane/footer";
|
|
11
12
|
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
12
13
|
import { useAsyncResource } from "../../../react/async-resource";
|
|
13
14
|
import type { PaneProps } from "../../../types/plugin";
|
|
@@ -61,7 +62,7 @@ export function FearGreedPane({ paneId, focused, width, height }: PaneProps) {
|
|
|
61
62
|
],
|
|
62
63
|
}] : []),
|
|
63
64
|
...(stale ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []),
|
|
64
|
-
...(updatedAgo ? [{ id: "age",
|
|
65
|
+
...(updatedAgo ? [ageFooterSegment(updatedAgo, { id: "age", verb: "updated", tone: "value" })] : []),
|
|
65
66
|
], [data, stale, updatedAgo]);
|
|
66
67
|
usePaneStatusFooter({ registrationId: paneId, loading, error, info: footerInfo });
|
|
67
68
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SeriesDefinition, SeriesLoadResult, SeriesPoint } from "../../../series/types";
|
|
2
|
-
import {
|
|
2
|
+
import { savedCopyForTransientFailure } from "../../../data/plugin-cache";
|
|
3
|
+
import { cachedSeriesNote } from "../shared/series-warning";
|
|
3
4
|
import { loadFearGreed } from "./cache";
|
|
4
5
|
import type { FearGreedData } from "./data";
|
|
5
6
|
|
|
@@ -30,9 +31,10 @@ function series(id: string, name: string, unit: string, read: Reading, tags: rea
|
|
|
30
31
|
tags: ["fear and greed", ...tags],
|
|
31
32
|
refreshMs: REFRESH_MS,
|
|
32
33
|
load: async (): Promise<SeriesLoadResult> => {
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
return { points: read(result.data),
|
|
34
|
+
// A saved copy answers only a failure asking again may get past; a refusal is the chart's error.
|
|
35
|
+
const result = savedCopyForTransientFailure(await loadFearGreed());
|
|
36
|
+
return { points: read(result.data),
|
|
37
|
+
...cachedSeriesNote({ ...result, ...(result.source === "stale-fallback" ? { savedAt: result.fetchedAt } : {}) }) };
|
|
36
38
|
},
|
|
37
39
|
};
|
|
38
40
|
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import type { PluginModule } from "../plugin-module";
|
|
2
2
|
import { treasuryAuctionsSettings } from "../treasury-auctions";
|
|
3
3
|
import { FIXED_INCOME_PANE_ID } from "./model";
|
|
4
|
-
import { FixedIncomePane } from "./pane";
|
|
5
4
|
|
|
6
5
|
export const fixedIncomeModule: PluginModule = {
|
|
7
6
|
panes: [{
|
|
8
7
|
id: FIXED_INCOME_PANE_ID,
|
|
9
8
|
name: "Fixed Income",
|
|
10
9
|
icon: "B",
|
|
11
|
-
component: FixedIncomePane,
|
|
10
|
+
get component(): typeof import("./pane").FixedIncomePane { const { FixedIncomePane } = require("./pane"); return FixedIncomePane; },
|
|
12
11
|
defaultPosition: "right",
|
|
13
12
|
defaultMode: "floating",
|
|
14
13
|
defaultFloatingSize: { width: 116, height: 32 },
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createConcurrencyLimiter } from "../../../utils/concurrency";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The board's contract histories (use-front-returns) and contract terms
|
|
5
|
+
* (use-board-terms) share one limit of twelve reads at a time, across both and
|
|
6
|
+
* every board open: seventy-odd rows load in about a second without the two
|
|
7
|
+
* crowding out each other or the quotes.
|
|
8
|
+
*/
|
|
9
|
+
const BOARD_READ_LIMIT = 12;
|
|
10
|
+
|
|
11
|
+
export const boardRead = createConcurrencyLimiter(BOARD_READ_LIMIT);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Reads that reach a source sending reads asked together as batch requests
|
|
15
|
+
* skip that limit: asked all at once, the API client sends a board's rows
|
|
16
|
+
* sixteen a request. This bound only keeps a runaway caller in check.
|
|
17
|
+
*/
|
|
18
|
+
const BOARD_BATCHED_READ_LIMIT = 200;
|
|
19
|
+
|
|
20
|
+
const boardBatchedRead = createConcurrencyLimiter(BOARD_BATCHED_READ_LIMIT);
|
|
21
|
+
|
|
22
|
+
/** The gate for the board's reads: twelve at a time, unless they go out in batch requests. */
|
|
23
|
+
export function boardReadGate(batched: boolean): typeof boardRead {
|
|
24
|
+
return batched ? boardBatchedRead : boardRead;
|
|
25
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** How long reads landing one after another are gathered into one redraw. */
|
|
2
|
+
const LANDING_GATHER_MS = 100;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A redraw for reads that land one by one (a board's seventy contract
|
|
6
|
+
* histories): the first landing redraws at once, and those in the next
|
|
7
|
+
* `LANDING_GATHER_MS` are drawn together when it ends, so the board redraws a
|
|
8
|
+
* few times rather than once per read. `flush` draws what is waiting at once
|
|
9
|
+
* (the last read landed) and `cancel` drops it (the board moved on).
|
|
10
|
+
*/
|
|
11
|
+
export function coalescedBump(redraw: () => void, gatherMs = LANDING_GATHER_MS) {
|
|
12
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
13
|
+
let waiting = false;
|
|
14
|
+
const gather = () => {
|
|
15
|
+
timer = setTimeout(() => {
|
|
16
|
+
timer = undefined;
|
|
17
|
+
if (!waiting) return;
|
|
18
|
+
waiting = false;
|
|
19
|
+
redraw();
|
|
20
|
+
gather();
|
|
21
|
+
}, gatherMs);
|
|
22
|
+
};
|
|
23
|
+
const cancel = () => {
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
timer = undefined;
|
|
26
|
+
waiting = false;
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
bump() {
|
|
30
|
+
if (timer !== undefined) {
|
|
31
|
+
waiting = true;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
redraw();
|
|
35
|
+
gather();
|
|
36
|
+
},
|
|
37
|
+
flush() {
|
|
38
|
+
const draw = waiting;
|
|
39
|
+
cancel();
|
|
40
|
+
if (draw) redraw();
|
|
41
|
+
},
|
|
42
|
+
cancel,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -25,9 +25,11 @@ import type { PluginModule } from "../plugin-module";
|
|
|
25
25
|
import { FUTURES_SERIES } from "./series";
|
|
26
26
|
import { useLiveStreamingSetting } from "../../../state/hooks/live-streaming";
|
|
27
27
|
import { quoteObservation } from "../shared/report-freshness";
|
|
28
|
+
import { useQuoteDelayFooter } from "../shared/cloud-upgrade";
|
|
28
29
|
import {
|
|
29
30
|
boardErrorMessage,
|
|
30
31
|
boardHeadlineQuote,
|
|
32
|
+
boardQuotes,
|
|
31
33
|
quoteBoardFooterInfo,
|
|
32
34
|
quoteBoardStatus,
|
|
33
35
|
useQuoteBoard,
|
|
@@ -46,6 +48,7 @@ import {
|
|
|
46
48
|
DEFAULT_FUTURES_SORT,
|
|
47
49
|
effectiveCollapsedSectors,
|
|
48
50
|
FUTURES_RETURN_COLUMNS,
|
|
51
|
+
futuresNoMatchFailure,
|
|
49
52
|
futuresRowId,
|
|
50
53
|
type FuturesColumnId,
|
|
51
54
|
type FuturesSortPreference,
|
|
@@ -255,15 +258,26 @@ export function FuturesPane({ focused, width, height }: PaneProps) {
|
|
|
255
258
|
},
|
|
256
259
|
} : null);
|
|
257
260
|
|
|
261
|
+
// `fn FUT natgas` with no contract to list fails with what to type instead, not the board's empty labels.
|
|
262
|
+
const noMatch = !!dataProvider && !!searchQuery.trim() && !rows.some((row) => row.type === "row");
|
|
263
|
+
useRemoteUiNode(noMatch ? {
|
|
264
|
+
role: "report-failure",
|
|
265
|
+
label: "No matching contracts",
|
|
266
|
+
getMetadata: () => futuresNoMatchFailure(searchQuery),
|
|
267
|
+
} : null);
|
|
268
|
+
|
|
258
269
|
const status = quoteBoardStatus(listedQuotes);
|
|
259
270
|
const errorMessage = boardErrorMessage(listedQuotes);
|
|
260
271
|
// Every load came back empty: a board of dashes reads as broken, so the
|
|
261
272
|
// body says so once and offers a retry. One quote is enough to keep rows.
|
|
262
273
|
const noQuotes = !!dataProvider && listedQuotes.size > 0 && status.unavailable === listedQuotes.size;
|
|
274
|
+
// Signed out or free, the contracts' quotes are delayed ones.
|
|
275
|
+
const quoteDelay = useQuoteDelayFooter(boardQuotes(listedQuotes), "futures-delay");
|
|
263
276
|
usePaneFooter(FUTURES_PANE_ID, () => {
|
|
264
277
|
if (noQuotes) return { info: [], hints: [] };
|
|
265
278
|
const info: PaneFooterSegment[] = quoteBoardFooterInfo(status);
|
|
266
279
|
if (errorMessage) info.push({ id: "reason", parts: [{ text: errorMessage, tone: "warning" }] });
|
|
280
|
+
if (quoteDelay) info.push(quoteDelay);
|
|
267
281
|
return {
|
|
268
282
|
info,
|
|
269
283
|
hints: [{ id: "search", key: "/", label: "search", onPress: focusSearch }],
|
|
@@ -272,6 +286,7 @@ export function FuturesPane({ focused, width, height }: PaneProps) {
|
|
|
272
286
|
errorMessage,
|
|
273
287
|
focusSearch,
|
|
274
288
|
noQuotes,
|
|
289
|
+
quoteDelay,
|
|
275
290
|
status.latestTs,
|
|
276
291
|
status.loading,
|
|
277
292
|
status.savedAt,
|
|
@@ -149,9 +149,23 @@ function matchesFuturesSearch(contract: FuturesContract, query: string, quote?:
|
|
|
149
149
|
|| contract.name.toLowerCase().includes(normalized)
|
|
150
150
|
|| futuresContractName(contract, quote).toLowerCase().includes(normalized)
|
|
151
151
|
|| contract.symbol.toLowerCase().includes(normalized)
|
|
152
|
+
// Everyday names the board's own does not hold: natgas is Natural Gas.
|
|
153
|
+
|| (contract.aliases ?? []).some((alias) => alias.includes(normalized))
|
|
152
154
|
);
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Why `fn FUT urea` lists nothing, for a search no contract matches: what
|
|
159
|
+
* to type instead, never the board's own buttons ("Clear search.").
|
|
160
|
+
*/
|
|
161
|
+
export function futuresNoMatchFailure(query: string): { message: string; details: string } {
|
|
162
|
+
return {
|
|
163
|
+
message: `No futures contract matches "${query.trim()}".`,
|
|
164
|
+
details: "Try a code such as NG, CL or GC, or a name such as natural gas or gold: gloom fn FUT NG."
|
|
165
|
+
+ " Other boards: gloom fn FUT --tab inventories, positioning or crops.",
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
155
169
|
export const FUTURES_RETURN_COLUMNS: Readonly<Partial<Record<FuturesColumnId, FuturesReturnHorizon>>> = {
|
|
156
170
|
return1w: "1W",
|
|
157
171
|
return1m: "1M",
|
|
@@ -5,6 +5,9 @@ import type { FuturesContract } from "../../../market-data/futures-board";
|
|
|
5
5
|
import { createTtlCache } from "../../../data/ttl-cache";
|
|
6
6
|
import { loadFuturesContractTerms } from "../../../market-data/futures-contract";
|
|
7
7
|
import { logFailure } from "../../../utils/log-failure";
|
|
8
|
+
import { coalescedBump } from "./coalesced-bump";
|
|
9
|
+
import { apiClient } from "../../../api-client";
|
|
10
|
+
import { boardReadGate } from "./board-reads";
|
|
8
11
|
|
|
9
12
|
interface FuturesTermsState {
|
|
10
13
|
/** The row's terms are still being asked for. */
|
|
@@ -23,8 +26,6 @@ const TERMS_TTL_MS = 30 * 60_000;
|
|
|
23
26
|
* once a minute, not once a tick.
|
|
24
27
|
*/
|
|
25
28
|
const TERMS_RETRY_MS = 60_000;
|
|
26
|
-
/** Forty-odd rows open at once; a few requests at a time keep the board's quotes first. */
|
|
27
|
-
const TERMS_CONCURRENCY = 4;
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Null when the server has no terms for the symbol, which is an answer and is
|
|
@@ -33,18 +34,18 @@ const TERMS_CONCURRENCY = 4;
|
|
|
33
34
|
*/
|
|
34
35
|
const termsCache = createTtlCache<FuturesContractTermsPayload | null>({ ttlMs: TERMS_TTL_MS, rejectionMs: TERMS_RETRY_MS });
|
|
35
36
|
|
|
36
|
-
async function readTerms(symbol: string): Promise<FuturesContractTermsPayload | null> {
|
|
37
|
+
async function readTerms(symbol: string, batch: boolean): Promise<FuturesContractTermsPayload | null> {
|
|
37
38
|
try {
|
|
38
|
-
return await loadFuturesContractTerms(symbol);
|
|
39
|
+
return await loadFuturesContractTerms(symbol, apiClient, { batch });
|
|
39
40
|
} catch (error) {
|
|
40
41
|
if (error instanceof ApiRequestError && error.status === 404) return null;
|
|
41
42
|
throw error;
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
/** Whether the row has an answer; false when the read failed. */
|
|
46
|
-
function loadTerms(symbol: string): Promise<boolean> {
|
|
47
|
-
return termsCache.load(symbol, () => readTerms(symbol))
|
|
46
|
+
/** Whether the row has an answer; false when the read failed. `batch`: asked with every other row's, in batch requests. */
|
|
47
|
+
function loadTerms(symbol: string, batch: boolean): Promise<boolean> {
|
|
48
|
+
return termsCache.load(symbol, () => readTerms(symbol, batch))
|
|
48
49
|
.then(() => true, logFailure("futures", "Read a futures row's contract terms failed", false));
|
|
49
50
|
}
|
|
50
51
|
|
|
@@ -67,18 +68,24 @@ export function useFuturesBoardTerms(contracts: readonly FuturesContract[], enab
|
|
|
67
68
|
let cancelled = false;
|
|
68
69
|
let failed = false;
|
|
69
70
|
let retryTimer: ReturnType<typeof setTimeout> | undefined;
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
const landed = coalescedBump(() => {
|
|
72
|
+
if (!cancelled) setVersion((current) => current + 1);
|
|
73
|
+
});
|
|
74
|
+
// Twelve reads at a time, shared with the rows' histories (`boardRead`), or all at once while the client
|
|
75
|
+
// sends terms in batch requests; a board that moved on skips its turn.
|
|
76
|
+
const batch = apiClient.batchesReads("futures-terms");
|
|
77
|
+
const read = boardReadGate(batch);
|
|
78
|
+
void Promise.all(queue.map((symbol) => read(async () => {
|
|
79
|
+
if (cancelled) return;
|
|
80
|
+
if (!await loadTerms(symbol, batch)) failed = true;
|
|
81
|
+
landed.bump();
|
|
82
|
+
}))).then(() => {
|
|
83
|
+
landed.flush();
|
|
77
84
|
if (failed && !cancelled) retryTimer = setTimeout(() => setRetry((current) => current + 1), TERMS_RETRY_MS);
|
|
78
85
|
});
|
|
79
86
|
return () => {
|
|
80
87
|
cancelled = true;
|
|
81
|
-
|
|
88
|
+
landed.cancel();
|
|
82
89
|
clearTimeout(retryTimer);
|
|
83
90
|
};
|
|
84
91
|
}, [enabled, symbolKey, retry]);
|