@gloom-sh/terminal 0.19.2 → 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 +1 -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 +20 -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.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +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 +92 -23
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +9 -4
- package/src/cli/commands/search.ts +71 -12
- 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 +5 -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/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +154 -4
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +40 -5
- package/src/cli/listing-arg.ts +46 -9
- 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 +20 -0
- 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 +24 -1
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/header.tsx +8 -3
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +7 -2
- package/src/components/layout/pane/footer/model.ts +40 -5
- 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 +2 -1
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +54 -23
- 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/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +8 -1
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +34 -31
- package/src/data/sqlite/access.ts +19 -8
- package/src/data/sqlite/checkpointer.ts +95 -8
- package/src/data/sqlite/database.ts +40 -7
- 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/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/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/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -2
- 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 +26 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -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/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/index.tsx +1 -2
- 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/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- 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 +2 -0
- 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/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/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- 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-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/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- 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/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 +15 -2
- 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/help/index.tsx +1 -1
- 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/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/index.tsx +7 -1
- package/src/plugins/builtin/market-movers/session-body.tsx +4 -1
- 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/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/index.tsx +1 -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/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/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 +60 -0
- 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/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- 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/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- 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 +116 -13
- package/src/plugins/builtin/thirteenf/data.ts +132 -30
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +20 -11
- package/src/plugins/builtin/thirteenf/types.ts +12 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +17 -4
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/headless.ts +11 -3
- 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/pane.tsx +2 -2
- 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/index.tsx +1 -2
- 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/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 +15 -4
- 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 +2 -0
- 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/initialization.ts +3 -2
- 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/create.ts +4 -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/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/standalone.ts +10 -1
- package/src/series/types.ts +7 -0
- package/src/series/validate.ts +11 -2
- 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 +12 -7
- package/src/telemetry/stall-reports.ts +34 -5
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name.ts +19 -0
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +13 -2
- package/src/tickers/search/index.ts +4 -1
- 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/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +55 -6
- package/src/time-series/resolve.ts +12 -3
- 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/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +22 -0
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +11 -0
- 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/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/main-thread-stall.ts +75 -3
- package/src/utils/perf-marks.ts +36 -2
- 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/renderers/electrobun/launcher/preload.ts +0 -13
- package/src/renderers/electrobun/launcher/windows-emulation.ts +0 -50
- package/src/time-series/studies.ts +0 -1628
|
@@ -28,8 +28,12 @@ import { reportCrash } from "./crash-reports";
|
|
|
28
28
|
* for the desktop app's own process, whether the app was in front and had a
|
|
29
29
|
* window on screen: a hidden app's slowed timers read like a freeze. A stall
|
|
30
30
|
* after a SQLite statement names the statement (a fixed name), its tries and
|
|
31
|
-
* whether it waited on another program's lock.
|
|
32
|
-
*
|
|
31
|
+
* whether it waited on another program's lock. Where the platform's clocks
|
|
32
|
+
* can be read, it says how much of the stall the thread spent on the CPU
|
|
33
|
+
* and how busy the process's other threads were, which tells script or a
|
|
34
|
+
* collection (busy) from a wait on the disk, a lock or another thread
|
|
35
|
+
* (idle); the time the computer slept never counts as a stall (see
|
|
36
|
+
* utils/main-thread-stall.ts). All of it is buckets and flags.
|
|
33
37
|
*
|
|
34
38
|
* One report per process, bucket, section and app version a day; stalls of
|
|
35
39
|
* the same kind after that are counted and carried in the next report, and
|
|
@@ -66,6 +70,10 @@ export interface StallReport {
|
|
|
66
70
|
attempts?: number;
|
|
67
71
|
/** After a SQLite statement: whether it waited on another connection's lock. */
|
|
68
72
|
lockWait?: boolean;
|
|
73
|
+
/** Where the platform says: the share of the stall the thread was on the CPU, in percent, by tens. */
|
|
74
|
+
cpuPercent?: number;
|
|
75
|
+
/** Where the platform says: the CPU the process's other threads used meanwhile, in percent of one core, by tens. */
|
|
76
|
+
otherCpuPercent?: number;
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
type StallBucket = "1-2s" | "2-5s" | "5-15s" | "15s+";
|
|
@@ -115,6 +123,8 @@ const MAX_KINDS_KEPT = 200;
|
|
|
115
123
|
const MAX_RECENT_KEPT = 500;
|
|
116
124
|
/** What error tracking keeps of the details line; fields that do not fit are left out. */
|
|
117
125
|
const MAX_DETAILS_LENGTH = 120;
|
|
126
|
+
/** Other threads' CPU past this many percent of a core reads as this. */
|
|
127
|
+
const MAX_OTHER_CPU_PERCENT = 990;
|
|
118
128
|
|
|
119
129
|
let host: StallReporterHost | null = null;
|
|
120
130
|
let attentionProbe: (() => StallAttention) | null = null;
|
|
@@ -198,20 +208,32 @@ function flag(value: boolean | null | undefined): string {
|
|
|
198
208
|
return value == null ? "?" : value ? "1" : "0";
|
|
199
209
|
}
|
|
200
210
|
|
|
211
|
+
/** A share or a core count in percent, by tens. */
|
|
212
|
+
function tens(value: number | undefined, max: number): number | undefined {
|
|
213
|
+
if (value === undefined || !Number.isFinite(value)) return undefined;
|
|
214
|
+
return Math.min(max, Math.max(0, Math.round(value * 10) * 10));
|
|
215
|
+
}
|
|
216
|
+
|
|
201
217
|
/** The context a report carries, also written at the end of the log line. */
|
|
202
218
|
interface StallContext {
|
|
203
219
|
phase: StallReport["phase"];
|
|
204
220
|
uptime: StallReport["uptime"];
|
|
205
221
|
gap: GapBucket;
|
|
206
222
|
attention: StallAttention | null;
|
|
223
|
+
cpuPercent?: number;
|
|
224
|
+
otherCpuPercent?: number;
|
|
207
225
|
}
|
|
208
226
|
|
|
209
227
|
function stallContext(event: StallEvent, attention: StallAttention | null): StallContext {
|
|
228
|
+
const cpuPercent = tens(event.threadCpu, 100);
|
|
229
|
+
const otherCpuPercent = tens(event.otherCpu, MAX_OTHER_CPU_PERCENT);
|
|
210
230
|
return {
|
|
211
231
|
phase: event.phase ?? "unknown",
|
|
212
232
|
uptime: uptimeBucket(event.uptimeMs),
|
|
213
233
|
gap: gapBucket(event),
|
|
214
234
|
attention,
|
|
235
|
+
...(cpuPercent === undefined ? {} : { cpuPercent }),
|
|
236
|
+
...(otherCpuPercent === undefined ? {} : { otherCpuPercent }),
|
|
215
237
|
};
|
|
216
238
|
}
|
|
217
239
|
|
|
@@ -229,6 +251,8 @@ function formatStallLogLine(process: StallProcess, event: StallEvent, context: S
|
|
|
229
251
|
`up=${context.uptime}`,
|
|
230
252
|
`gap=${context.gap}`,
|
|
231
253
|
...(context.attention ? [`fg=${flag(context.attention.active)}`, `vis=${flag(context.attention.visible)}`] : []),
|
|
254
|
+
...(context.cpuPercent === undefined ? [] : [`cpu=${context.cpuPercent}`]),
|
|
255
|
+
...(context.otherCpuPercent === undefined ? [] : [`oth=${context.otherCpuPercent}`]),
|
|
232
256
|
].join(" ");
|
|
233
257
|
return `${time} ${process.padEnd(8)} froze ${formatSeconds(event.blockedMs).padStart(6)} ${last}${sqlite} [${facts}]`;
|
|
234
258
|
}
|
|
@@ -245,23 +269,26 @@ function reportArch(arch: string): StallReport["arch"] {
|
|
|
245
269
|
}
|
|
246
270
|
|
|
247
271
|
/**
|
|
248
|
-
* The details line error tracking keeps, most telling first
|
|
249
|
-
*
|
|
272
|
+
* The details line error tracking keeps, most telling first. The platform
|
|
273
|
+
* comes last: error tracking has the system and its architecture already.
|
|
274
|
+
* One that would not fit is left out.
|
|
250
275
|
*/
|
|
251
276
|
export function formatStallReportDetails(report: StallReport): string {
|
|
252
277
|
const fields = [
|
|
253
278
|
`process=${report.process}`,
|
|
254
279
|
`count=${report.count}`,
|
|
255
280
|
`longest=${report.longestMs}ms`,
|
|
256
|
-
`platform=${report.platform}-${report.arch}`,
|
|
257
281
|
`phase=${report.phase}`,
|
|
258
282
|
`up=${report.uptime}`,
|
|
259
283
|
`gap=${report.gap}`,
|
|
260
284
|
...(report.active === undefined ? [] : [`fg=${flag(report.active)}`]),
|
|
261
285
|
...(report.visible === undefined ? [] : [`vis=${flag(report.visible)}`]),
|
|
286
|
+
...(report.cpuPercent === undefined ? [] : [`cpu=${report.cpuPercent}`]),
|
|
287
|
+
...(report.otherCpuPercent === undefined ? [] : [`oth=${report.otherCpuPercent}`]),
|
|
262
288
|
...(report.lockWait === undefined ? [] : [`lock=${flag(report.lockWait)}`]),
|
|
263
289
|
...(report.attempts === undefined ? [] : [`try=${report.attempts}`]),
|
|
264
290
|
`day=${report.day}`,
|
|
291
|
+
`platform=${report.platform}-${report.arch}`,
|
|
265
292
|
];
|
|
266
293
|
let details = "";
|
|
267
294
|
for (const field of fields) {
|
|
@@ -357,6 +384,8 @@ export function recordStall(process: StallProcess, event: StallEvent): void {
|
|
|
357
384
|
uptime: context.uptime,
|
|
358
385
|
gap: context.gap,
|
|
359
386
|
...(context.attention ? { active: context.attention.active, visible: context.attention.visible } : {}),
|
|
387
|
+
...(context.cpuPercent === undefined ? {} : { cpuPercent: context.cpuPercent }),
|
|
388
|
+
...(context.otherCpuPercent === undefined ? {} : { otherCpuPercent: context.otherCpuPercent }),
|
|
360
389
|
...(event.sqlite && section.startsWith("sqlite.blocked")
|
|
361
390
|
? { attempts: Math.max(1, Math.min(99, Math.round(event.sqlite.attempts))), lockWait: event.sqlite.lockWait }
|
|
362
391
|
: {}),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { beforeEach } from "bun:test";
|
|
2
2
|
import { resetCrashReporterForTests } from "../telemetry/crash-reports";
|
|
3
|
+
import { setCliAsciiLocaleOverride, setCliColorEnabledOverride, setCliPlainOverride, setCliWidthOverride } from "../utils/cli-output";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* `bun test` runs every file in one process, so module state outlives the file
|
|
@@ -8,7 +9,15 @@ import { resetCrashReporterForTests } from "../telemetry/crash-reports";
|
|
|
8
9
|
* queue and go out with the next reporter another file installs, and its key
|
|
9
10
|
* would keep a later identical report from being queued at all. Every test
|
|
10
11
|
* starts with no reporter, an empty queue and nothing seen.
|
|
12
|
+
*
|
|
13
|
+
* Commands also keep output overrides for the process: a parity test that
|
|
14
|
+
* runs --plain would otherwise make a later serializer test keep empty
|
|
15
|
+
* columns. Start each test with the default output modes and terminal width.
|
|
11
16
|
*/
|
|
12
17
|
beforeEach(() => {
|
|
13
18
|
resetCrashReporterForTests();
|
|
19
|
+
setCliAsciiLocaleOverride(null);
|
|
20
|
+
setCliColorEnabledOverride(null);
|
|
21
|
+
setCliPlainOverride(null);
|
|
22
|
+
setCliWidthOverride(null);
|
|
14
23
|
});
|
|
@@ -204,6 +204,25 @@ export function groupByCompany<T>(
|
|
|
204
204
|
return groups;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
/**
|
|
208
|
+
* A one-letter code a venue pads onto the end of a name after a run of
|
|
209
|
+
* spaces: the `R` of "Americold Realty Trust Inc. R". A longer word there
|
|
210
|
+
* (`AG`, `ASA`) is the name's own.
|
|
211
|
+
*/
|
|
212
|
+
const STRAY_NAME_TAIL = /\s{2,}[A-Za-z0-9]$/;
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* A listing's name as a venue's file writes it, without the stray fragment a
|
|
216
|
+
* fixed-width file leaves after a run of spaces ("Americold Realty Trust
|
|
217
|
+
* Inc. R" on Hanover is "Americold Realty Trust Inc."), and with its spaces
|
|
218
|
+
* single. A name that is only the fragment keeps it.
|
|
219
|
+
*/
|
|
220
|
+
export function cleanListingName(name: string): string {
|
|
221
|
+
const trimmed = name.trim();
|
|
222
|
+
const cut = trimmed.replace(STRAY_NAME_TAIL, "");
|
|
223
|
+
return (cut || trimmed).replace(/\s+/g, " ");
|
|
224
|
+
}
|
|
225
|
+
|
|
207
226
|
/** A company's name without its legal form, for sentences: "Avis Budget Group, Inc." is "Avis Budget Group". */
|
|
208
227
|
export function shortCompanyName(name: string): string {
|
|
209
228
|
const words = name.trim().split(/\s+/).filter(Boolean);
|
|
@@ -132,6 +132,12 @@ export function listingChangeLine(fate: Pick<ListingFate, "change" | "formerIssu
|
|
|
132
132
|
export const NO_FATE: ListingFate = { endedOn: null, change: null, note: null, moved: null, formerIssuer: null };
|
|
133
133
|
/** How long a fate waits for a change lookup it started late, or for a stopped line's. */
|
|
134
134
|
export const LATE_CHANGE_WAIT_MS = 2_000;
|
|
135
|
+
/**
|
|
136
|
+
* How long a command that says what became of a symbol's company (GOGL's merger) waits for the lookup it started
|
|
137
|
+
* beside its own request, once that is in: the lookup takes about a third of a second, and a cached request answers
|
|
138
|
+
* at once. A lookup that hangs costs a symbol that trades no more than this.
|
|
139
|
+
*/
|
|
140
|
+
export const SUCCESSOR_CHANGE_WAIT_MS = 750;
|
|
135
141
|
|
|
136
142
|
// The zones of the US exchanges; a venue in another is abroad.
|
|
137
143
|
const US_TIME_ZONES = new Set(["America/New_York", "America/Chicago"]);
|
|
@@ -195,7 +201,11 @@ export function lineMayHaveMoved(quote: Quote | null, now = Date.now()): boolean
|
|
|
195
201
|
export async function loadListingFate(
|
|
196
202
|
listing: { symbol: string; exchange?: string | null; request?: { symbol: string; exchange: string } },
|
|
197
203
|
dataProvider: Pick<DataProvider, "getQuote" | "getTickerChanges">,
|
|
198
|
-
|
|
204
|
+
/**
|
|
205
|
+
* `graceMs`: how long a lookup the command started waits once the quote is in, for a command that says what
|
|
206
|
+
* became of the company beside what it serves (GOGL's merger), not only whether it moved. A short grace by default.
|
|
207
|
+
*/
|
|
208
|
+
options: { quote?: Quote | null | Promise<Quote | null>; changes?: TickerChangeLookup; graceMs?: number } = {},
|
|
199
209
|
): Promise<ListingFate> {
|
|
200
210
|
const request = listing.request ?? { symbol: listing.symbol, exchange: listing.exchange ?? "" };
|
|
201
211
|
const lookup = options.changes ?? startTickerChangeLookup(dataProvider, listing.symbol, listing.exchange);
|
|
@@ -206,7 +216,7 @@ export async function loadListingFate(
|
|
|
206
216
|
// A lookup started beside the command's request gets a short grace once that is ready, so a slow
|
|
207
217
|
// one never holds a symbol that trades. A line that stopped is waited for: only its company can
|
|
208
218
|
// have moved, and then the answer decides whether the command serves anything at all.
|
|
209
|
-
const changes = await lookup.settle(!options.changes || lineMayHaveMoved(quote) ? LATE_CHANGE_WAIT_MS :
|
|
219
|
+
const changes = await lookup.settle(!options.changes || lineMayHaveMoved(quote) ? LATE_CHANGE_WAIT_MS : options.graceMs);
|
|
210
220
|
return describeListingFate(listing, quote, changes);
|
|
211
221
|
} catch (error) {
|
|
212
222
|
logFailure("tickers-listing-fate", "Could not resolve listing fate")(error);
|
|
@@ -230,7 +240,8 @@ export function listingFateNotes(fate: ListingFate, served: { served: boolean; f
|
|
|
230
240
|
return [
|
|
231
241
|
// What it served ends on the listing's last session, and says so.
|
|
232
242
|
fate.delisted ?? null,
|
|
233
|
-
|
|
243
|
+
// A company that left the symbol, else one that merged away from it (GOGL), as `quote` says it.
|
|
244
|
+
listingChangeLine(fate),
|
|
234
245
|
earlier.length > 0 ? `Includes ${earlier.join("- and ")}-era bars: ${change!.note}` : null,
|
|
235
246
|
].filter((line): line is string => !!line);
|
|
236
247
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TickerRecord } from "../types/ticker";
|
|
2
|
+
import { parsePublicTickerKey } from "../utils/exchanges";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The listing a typed key stands for in a report (`resolveInstrument`): the
|
|
6
|
+
* venue it names (`SBK:JSE`), else the venue of the ticker saved under the key
|
|
7
|
+
* or under its bare symbol, so a saved `S32` held on the JSE is read there.
|
|
8
|
+
* `fn`, `shot` and the panes that read as they do resolve keys this way.
|
|
9
|
+
*/
|
|
10
|
+
export async function resolveSavedInstrument(
|
|
11
|
+
key: string,
|
|
12
|
+
loadTicker: (key: string) => Promise<TickerRecord | null | undefined> | TickerRecord | null | undefined,
|
|
13
|
+
): Promise<{ symbol: string; exchange?: string }> {
|
|
14
|
+
const parsed = parsePublicTickerKey(key);
|
|
15
|
+
const ticker = await loadTicker(key) ?? (key !== parsed.symbol ? await loadTicker(parsed.symbol) : null);
|
|
16
|
+
return { symbol: parsed.symbol, exchange: parsed.exchange ?? ticker?.metadata.exchange };
|
|
17
|
+
}
|
|
@@ -122,8 +122,11 @@ function isListedEquity(candidate: TickerSearchCandidate): boolean {
|
|
|
122
122
|
* Siemens AG). The row the service marks with the word as typed is the
|
|
123
123
|
* company the word means, ahead of a line abroad whose symbol is the word:
|
|
124
124
|
* "siemens" is SIE on XETRA, not SIEMENS on NSE (Siemens Ltd.); a US line
|
|
125
|
-
* keeps its ticker. A
|
|
126
|
-
*
|
|
125
|
+
* keeps its ticker. A symbol typed whose first row is another company's
|
|
126
|
+
* regional line abroad leads with the line the service marks as a home under
|
|
127
|
+
* that symbol (YAR is Yara on OSL, not Americold on Hanover). A query that
|
|
128
|
+
* names a venue (LVMH:TSX, 7203.T) asked for that line, and a saved row keeps
|
|
129
|
+
* the lead.
|
|
127
130
|
*/
|
|
128
131
|
export function withHomeListingLead<T extends TickerSearchCandidate>(items: T[], query: string): T[] {
|
|
129
132
|
if (isQualifiedTickerQuery(query)) return items;
|
|
@@ -137,6 +140,14 @@ export function withHomeListingLead<T extends TickerSearchCandidate>(items: T[],
|
|
|
137
140
|
&& item.symbol.toUpperCase() !== lead.symbol.toUpperCase() && namedByService(query, item.result))
|
|
138
141
|
: undefined;
|
|
139
142
|
if (meant) return moveAhead(items, meant, lead);
|
|
143
|
+
// A line abroad that only quotes shares listed elsewhere (a German regional venue) gives way to the line the
|
|
144
|
+
// service marks as a company's home under the same symbol: YAR is Yara on Oslo, not Americold on Hanover.
|
|
145
|
+
const homeOfSymbol = typedSymbol && lead.result?.homeListing !== true && listingDistance(lead) === 1
|
|
146
|
+
&& !isUsListingExchange(candidateExchange(lead))
|
|
147
|
+
? items.find((item) => item !== lead && isListedEquity(item) && item.result?.homeListing === true
|
|
148
|
+
&& item.symbol.toUpperCase() === lead.symbol.toUpperCase())
|
|
149
|
+
: undefined;
|
|
150
|
+
if (homeOfSymbol) return moveAhead(items, homeOfSymbol, lead);
|
|
140
151
|
const name = candidateName(lead);
|
|
141
152
|
const lines = companyRows(lead, items).filter((item) => item !== lead);
|
|
142
153
|
const nearest = (candidates: T[]) => [...candidates].sort((a, b) => nameGap(name, candidateName(a)) - nameGap(name, candidateName(b))
|
|
@@ -507,7 +507,10 @@ async function resolveTickerSymbol(
|
|
|
507
507
|
throw new AmbiguousContractError(symbol, matches.map((item) => tickerInstrumentLabel(item.symbol, item.result?.brokerContract)));
|
|
508
508
|
}
|
|
509
509
|
const listings = new Set(matches.map((item) => publicTickerKey(item.symbol, listingExchange(item.result!))));
|
|
510
|
-
|
|
510
|
+
// A line the service marks as a company's home leads the choices: YAR is Yara on Oslo before Americold's German lines.
|
|
511
|
+
const homeFirst = [...matches.filter((item) => item.result?.homeListing === true), ...matches.filter((item) => item.result?.homeListing !== true)];
|
|
512
|
+
const choices = new Set(homeFirst.map((item) => publicTickerKey(item.symbol, listingExchange(item.result!))));
|
|
513
|
+
const ambiguity = (query: string) => new AmbiguousTickerError(query, [...choices], Object.fromEntries(matches.map((item) => [
|
|
511
514
|
publicTickerKey(item.symbol, listingExchange(item.result!)), listingChoiceName(item.result!),
|
|
512
515
|
])));
|
|
513
516
|
if (pickerQuery && listings.size > 1) throw ambiguity(pickerQuery);
|
|
@@ -3,7 +3,12 @@ import type { Quote } from "../../types/financials";
|
|
|
3
3
|
import type { TickerRecord } from "../../types/ticker";
|
|
4
4
|
|
|
5
5
|
export type TickerSearchInstrumentClass = "equity" | "fund" | "derivative" | "other";
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* `Other Matches`: rows `gloom search` finds for the query that are not a listing
|
|
8
|
+
* of the primary listing's company (another company's DK, a line search names
|
|
9
|
+
* no company for), kept apart from its Other Listings.
|
|
10
|
+
*/
|
|
11
|
+
type TickerSearchCategory = "Saved" | "Primary Listing" | "Other Listings" | "Other Matches" | "Funds & Derivatives" | "Related";
|
|
7
12
|
|
|
8
13
|
export interface TickerSearchRankableItem {
|
|
9
14
|
id: string;
|
|
@@ -182,6 +182,8 @@ function buildBar(bucket: Bucket, intraday: boolean): AggregatedPricePoint {
|
|
|
182
182
|
let low = Number.POSITIVE_INFINITY;
|
|
183
183
|
let volume: number | undefined;
|
|
184
184
|
let venueVolume: PricePoint["venueVolume"] | null | undefined;
|
|
185
|
+
// What the volume counts carries over while every bar counts the same way.
|
|
186
|
+
let volumeBasis: string | null | undefined;
|
|
185
187
|
let partial = false;
|
|
186
188
|
let filled = false;
|
|
187
189
|
const integrity: PriceHistoryIntegrity[] = [];
|
|
@@ -189,7 +191,10 @@ function buildBar(bucket: Bucket, intraday: boolean): AggregatedPricePoint {
|
|
|
189
191
|
const close = bar.close;
|
|
190
192
|
high = Math.max(high, isFiniteNumber(bar.high) ? bar.high : close);
|
|
191
193
|
low = Math.min(low, isFiniteNumber(bar.low) ? bar.low : close);
|
|
192
|
-
if (isFiniteNumber(bar.volume))
|
|
194
|
+
if (isFiniteNumber(bar.volume)) {
|
|
195
|
+
volume = (volume ?? 0) + bar.volume;
|
|
196
|
+
volumeBasis = volumeBasis === undefined || volumeBasis === bar.volumeBasis ? bar.volumeBasis ?? null : null;
|
|
197
|
+
}
|
|
193
198
|
// One venue's trades add up only while every bar counts the same venue.
|
|
194
199
|
if (bar.venueVolume && venueVolume !== null) {
|
|
195
200
|
venueVolume = venueVolume === undefined || venueVolume.venue === bar.venueVolume.venue
|
|
@@ -207,6 +212,7 @@ function buildBar(bucket: Bucket, intraday: boolean): AggregatedPricePoint {
|
|
|
207
212
|
low,
|
|
208
213
|
close: last.close,
|
|
209
214
|
...(volume !== undefined ? { volume } : {}),
|
|
215
|
+
...(volume !== undefined && volumeBasis ? { volumeBasis } : {}),
|
|
210
216
|
...(volume === undefined && venueVolume ? { venueVolume } : {}),
|
|
211
217
|
...(partial ? { partial: true } : {}),
|
|
212
218
|
...(filled ? { filled: true } : {}),
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
valuationPriceIssues,
|
|
16
16
|
} from "./fundamentals";
|
|
17
17
|
import { extractSecuritySeries } from "./market";
|
|
18
|
-
import { activeStudyInputSeriesIds } from "./studies";
|
|
18
|
+
import { activeStudyInputSeriesIds } from "./studies/resolve";
|
|
19
19
|
import { reportingCurrencySeries } from "./reporting-currency";
|
|
20
20
|
import { compareChartQuoteRecency } from "./live-quotes";
|
|
21
21
|
import { marketSeriesFrequency } from "./market";
|
|
@@ -138,8 +138,8 @@ export function baseSecuritySeries(
|
|
|
138
138
|
: field.unit;
|
|
139
139
|
const generic = field.unitGroup === "price"
|
|
140
140
|
? futuresGenericListing(spec.source.instrument.symbol, spec.source.instrument.exchange) : null;
|
|
141
|
-
//
|
|
142
|
-
//
|
|
141
|
+
// Index and Treasury futures read in points or 32nds across generics,
|
|
142
|
+
// continuous symbols and listed contracts, as their quotes do.
|
|
143
143
|
const genericBasis = field.unitGroup === "price" && isMarketFieldId(field.id)
|
|
144
144
|
? futuresListingPriceBasis(spec.source.instrument.symbol, spec.source.instrument.exchange) : null;
|
|
145
145
|
// An index's level is points too, never the currency its members trade in.
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
type ManualChartResolution,
|
|
16
16
|
} from "./resolution";
|
|
17
17
|
import { TIME_RANGES, type TimeRange } from "./range";
|
|
18
|
-
import { maxStudyWarmupPoints } from "./studies";
|
|
18
|
+
import { maxStudyWarmupPoints } from "./studies/resolve";
|
|
19
19
|
import type { PriceComparison } from "./price-comparison";
|
|
20
20
|
import { resolutionForExplicitMarketPeriods } from "./market-resolution";
|
|
21
21
|
import { latestSessionStart, latestTradingSessionOpen, priorSessionClose } from "../market-data/market/trading-sessions";
|
|
@@ -103,8 +103,11 @@ function finiteOrFallback(value: number | undefined, fallback: number): number {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
function mergeQuoteIntoLatestBar(latest: PricePoint, quotePrice: number, range?: QuoteSessionRange | null): PricePoint {
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
if (pricePointIntegrity(latest)) {
|
|
107
|
+
// A later quote cannot establish which reported OHLC field was wrong, unless it names the bar's whole session: its
|
|
108
|
+
// open, range and price are the bar (GOGL's 2026-10-30 daily bar, served with a high under its open).
|
|
109
|
+
return range?.open === undefined ? latest : { ...latest, open: range.open, high: range.high, low: range.low, close: quotePrice };
|
|
110
|
+
}
|
|
108
111
|
const open = finiteOrFallback(latest.open, latest.close);
|
|
109
112
|
const high = finiteOrFallback(latest.high, Math.max(open, latest.close));
|
|
110
113
|
const low = finiteOrFallback(latest.low, Math.min(open, latest.close));
|
|
@@ -10,7 +10,7 @@ import { aggregatePriceBars, futuresOpenMinuteOf, withBuiltBarSize } from "./bar
|
|
|
10
10
|
import type { TickerFinancials } from "../types/financials";
|
|
11
11
|
import { isMarketFieldId } from "./field-catalog";
|
|
12
12
|
import { chartPriceHistoryIntegrityNotices } from "./market";
|
|
13
|
-
import { resolveStudies } from "./studies";
|
|
13
|
+
import { resolveStudies } from "./studies/resolve";
|
|
14
14
|
import { clipSeriesToWindow } from "./alignment";
|
|
15
15
|
import { bufferPriceComparison, clipPriceComparison, priceComparisonBoundsForSeries, resolvePriceComparison } from "./price-comparison";
|
|
16
16
|
import { readSeedListingFacts } from "./parsed-history-cache";
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { hasRegularCloseHour, isAlwaysOpenExchange, regularSessionCloseOn, sessionCalendarTimeZone } from "../market-data/market/freshness";
|
|
2
|
+
import { fetchHistoryResult } from "../sources/history-result";
|
|
3
|
+
import type { DataProvider } from "../types/data-provider";
|
|
4
|
+
import { isRoundTheClockCoin } from "../utils/crypto-pair";
|
|
5
|
+
import { canonicalExchange, isUsListingExchange } from "../utils/exchanges";
|
|
6
|
+
import { logFailure } from "../utils/log-failure";
|
|
7
|
+
import { zonedDateTimeParts, zonedWallClockToUtcMs } from "../utils/zoned-date-time";
|
|
8
|
+
import type { PriceComparison } from "./price-comparison";
|
|
9
|
+
import type { ResolvedSeries } from "./types";
|
|
10
|
+
|
|
11
|
+
const HOUR_MS = 60 * 60_000;
|
|
12
|
+
const DAY_MS = 24 * HOUR_MS;
|
|
13
|
+
/** How far back hourly bars are read for a comparison: Gloom Cloud keeps a coin's for a year. A longer span keeps UTC closes. */
|
|
14
|
+
const HOURLY_REACH_MS = 370 * DAY_MS;
|
|
15
|
+
|
|
16
|
+
/** The venue a compared leg trades on, as its sessions are read. */
|
|
17
|
+
function legVenue(series: ResolvedSeries | undefined): string | undefined {
|
|
18
|
+
return series?.timeBasis?.exchange || series?.listing?.exchange || undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** A coin that trades round the clock, its daily bar the UTC day: BTC-USD on the crypto venue. */
|
|
22
|
+
function isRoundTheClockLeg(series: ResolvedSeries | undefined): boolean {
|
|
23
|
+
if (!series) return false;
|
|
24
|
+
const venue = legVenue(series);
|
|
25
|
+
return (!!venue && isAlwaysOpenExchange(venue)) || isRoundTheClockCoin(series.listing?.symbol, series.listing?.exchange);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A leg whose venue closes each day at a known hour: a US fund, a London share. A venue whose close hour is not
|
|
30
|
+
* known has no close to measure a coin at: the coin keeps its UTC closes, and the notice names no close.
|
|
31
|
+
*/
|
|
32
|
+
function sessionVenueOf(series: ResolvedSeries | undefined): string | null {
|
|
33
|
+
const venue = legVenue(series);
|
|
34
|
+
return venue && !isRoundTheClockLeg(series) && !isAlwaysOpenExchange(venue) && sessionCalendarTimeZone(venue) && hasRegularCloseHour(venue)
|
|
35
|
+
? venue : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** `US` for a US listing's venue, else the venue's code, as the close a coin is measured against is named. */
|
|
39
|
+
function sessionMarketName(venue: string): string {
|
|
40
|
+
return isUsListingExchange(venue) ? "US" : canonicalExchange(venue);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type HourlyRead = Awaited<ReturnType<typeof fetchHistoryResult>>;
|
|
44
|
+
type HourlyProvider = Pick<DataProvider, "getDetailedPriceHistory" | "getDetailedPriceHistoryWithMetadata">;
|
|
45
|
+
interface HourlyReads {
|
|
46
|
+
/** Recent reads by listing and span: a chart resolves again on every live quote, and asks for the same hours. */
|
|
47
|
+
spans: Map<string, { at: number; read: Promise<HourlyRead> }>;
|
|
48
|
+
/** Listings whose read failed, until when they are not asked again. */
|
|
49
|
+
failedUntil: Map<string, number>;
|
|
50
|
+
}
|
|
51
|
+
const hourlyReadsBySource = new WeakMap<HourlyProvider, HourlyReads>();
|
|
52
|
+
const HOURLY_READ_LIMIT = 64;
|
|
53
|
+
/** A span reaching the last hour is read again after this long; an older one is kept until it falls out. */
|
|
54
|
+
const RECENT_READ_TTL_MS = 60_000;
|
|
55
|
+
/** A listing whose hourly bars failed to load is not asked again for this long, so the failure is logged once in it. */
|
|
56
|
+
const FAILED_READ_TTL_MS = 2 * 60_000;
|
|
57
|
+
const logHourlyFailure = logFailure("time-series.coin-session-close", "Read a coin's hourly bars for a comparison failed", null);
|
|
58
|
+
|
|
59
|
+
function hourlyBars(
|
|
60
|
+
provider: HourlyProvider,
|
|
61
|
+
symbol: string,
|
|
62
|
+
exchange: string,
|
|
63
|
+
start: number,
|
|
64
|
+
end: number,
|
|
65
|
+
now: number,
|
|
66
|
+
): Promise<HourlyRead> {
|
|
67
|
+
let reads = hourlyReadsBySource.get(provider);
|
|
68
|
+
if (!reads) hourlyReadsBySource.set(provider, reads = { spans: new Map(), failedUntil: new Map() });
|
|
69
|
+
const listing = `${symbol}:${exchange}`;
|
|
70
|
+
if ((reads.failedUntil.get(listing) ?? 0) > now) return Promise.resolve(null);
|
|
71
|
+
// A span ending now is keyed by its minute, so a chart redrawn within it reads it once.
|
|
72
|
+
end = Math.ceil(end / 60_000) * 60_000;
|
|
73
|
+
const key = `${listing}:${start}:${end}`;
|
|
74
|
+
const cached = reads.spans.get(key);
|
|
75
|
+
if (cached && (end < now - HOUR_MS || now - cached.at < RECENT_READ_TTL_MS)) return cached.read;
|
|
76
|
+
const read = fetchHistoryResult(provider as DataProvider, symbol, exchange, { kind: "detail", start: new Date(start), end: new Date(end), interval: "1h" })
|
|
77
|
+
.then((result) => result ?? Promise.reject(new Error("no hourly bars")))
|
|
78
|
+
.catch((error: unknown) => {
|
|
79
|
+
// A failed read is asked again only after FAILED_READ_TTL_MS, and logged once for it.
|
|
80
|
+
reads!.failedUntil.set(listing, now + FAILED_READ_TTL_MS);
|
|
81
|
+
if (reads!.spans.get(key)?.read === read) reads!.spans.delete(key);
|
|
82
|
+
return logHourlyFailure(error);
|
|
83
|
+
});
|
|
84
|
+
reads.spans.delete(key);
|
|
85
|
+
reads.spans.set(key, { at: now, read });
|
|
86
|
+
if (reads.spans.size > HOURLY_READ_LIMIT) reads.spans.delete(reads.spans.keys().next().value!);
|
|
87
|
+
return read;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface SessionCloseTiming {
|
|
91
|
+
/** The venue whose close the coins are compared against: the first compared leg that trades in sessions. */
|
|
92
|
+
venue: string;
|
|
93
|
+
/** Coin legs measured at that venue's close on each of their days, from hourly bars. */
|
|
94
|
+
atSessionClose: ReadonlySet<string>;
|
|
95
|
+
/** Of those, the legs whose newest day's session has closed: their end is that close, not a price still moving. */
|
|
96
|
+
closedEnds: ReadonlySet<string>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The venue's wall clock of `time` in its zone, to measure a day without a session at its usual close. */
|
|
100
|
+
function wallClock(time: number, zone: string): { hour: number; minute: number } {
|
|
101
|
+
const { hour, minute } = zonedDateTimeParts(time, zone);
|
|
102
|
+
return { hour, minute };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* A daily comparison of a coin with a listing that trades in sessions (IBIT
|
|
107
|
+
* against BTC-USD) compares the fund's close at 20:00 UTC with the coin's at
|
|
108
|
+
* 00:00 UTC, four hours later, which on a falling night is most of the gap.
|
|
109
|
+
* When the coin's hourly bars cover its whole span, every one of its daily
|
|
110
|
+
* points from the base to its end is replaced by its price at the session
|
|
111
|
+
* venue's close (the close of the hourly bar ending then; a day the venue
|
|
112
|
+
* does not trade, at the clock time of its last close), so both legs and
|
|
113
|
+
* the line between are measured at the same instants. A coin whose hourly
|
|
114
|
+
* bars miss any of those hours (they go back a year) keeps its UTC closes
|
|
115
|
+
* throughout, and the comparison names each close time instead
|
|
116
|
+
* (priceComparisonCloseTimes): the line and its total never mix the two.
|
|
117
|
+
*
|
|
118
|
+
* Null when the comparison has no such pair of legs; otherwise the venue and
|
|
119
|
+
* the series, with the coin legs that could be measured at its close.
|
|
120
|
+
*/
|
|
121
|
+
export async function coinLegsAtSessionClose(
|
|
122
|
+
comparison: PriceComparison | null,
|
|
123
|
+
series: readonly ResolvedSeries[],
|
|
124
|
+
provider: HourlyProvider,
|
|
125
|
+
now: number,
|
|
126
|
+
): Promise<{ timing: SessionCloseTiming; series: ResolvedSeries[] } | null> {
|
|
127
|
+
if (comparison?.alignment !== "session-date" || !comparison.legs) return null;
|
|
128
|
+
const byId = new Map(series.map((entry) => [entry.id, entry]));
|
|
129
|
+
const compared = Object.keys(comparison.legs);
|
|
130
|
+
const venue = compared.map((id) => sessionVenueOf(byId.get(id))).find((entry): entry is string => !!entry);
|
|
131
|
+
const coins = compared.filter((id) => isRoundTheClockLeg(byId.get(id)) && byId.get(id)!.historyResolution === "1d");
|
|
132
|
+
const zone = venue ? sessionCalendarTimeZone(venue) : null;
|
|
133
|
+
if (!venue || !zone || coins.length === 0) return null;
|
|
134
|
+
const atSessionClose = new Set<string>();
|
|
135
|
+
const closedEnds = new Set<string>();
|
|
136
|
+
const next = [...series];
|
|
137
|
+
await Promise.all(coins.map(async (id) => {
|
|
138
|
+
const entry = byId.get(id)!;
|
|
139
|
+
const leg = comparison.legs![id]!;
|
|
140
|
+
const symbol = entry.listing?.symbol;
|
|
141
|
+
const baseClose = regularSessionCloseOn(venue, leg.baseDay);
|
|
142
|
+
// A coin's newest day still running is its latest price; measured at the close only once the venue has closed.
|
|
143
|
+
const endClose = regularSessionCloseOn(venue, leg.endDay);
|
|
144
|
+
const endTime = endClose !== null && endClose <= now ? endClose : leg.intraday ? null : endClose;
|
|
145
|
+
if (!symbol || baseClose === null || (endTime === null && !leg.intraday)) return;
|
|
146
|
+
// The instant each of the coin's days is measured at: the venue's close that day, or on a day it does not trade,
|
|
147
|
+
// the clock time of its last close before.
|
|
148
|
+
const instants = new Map<number, number>();
|
|
149
|
+
let clock = wallClock(baseClose, zone);
|
|
150
|
+
entry.points.forEach((point, index) => {
|
|
151
|
+
const day = point.date.toISOString().slice(0, 10);
|
|
152
|
+
if (day < leg.baseDay || day > leg.endDay) return;
|
|
153
|
+
if (day === leg.endDay && endTime === null) return;
|
|
154
|
+
const close = regularSessionCloseOn(venue, day);
|
|
155
|
+
if (close !== null) clock = wallClock(close, zone);
|
|
156
|
+
instants.set(index, close ?? zonedWallClockToUtcMs(zone, Number(day.slice(0, 4)), Number(day.slice(5, 7)), Number(day.slice(8, 10)),
|
|
157
|
+
clock.hour, clock.minute, 0));
|
|
158
|
+
});
|
|
159
|
+
const times = [...instants.values()];
|
|
160
|
+
const last = Math.max(baseClose, ...times);
|
|
161
|
+
if (last > now || last - baseClose > HOURLY_REACH_MS) return;
|
|
162
|
+
const read = await hourlyBars(provider, symbol, entry.listing?.exchange ?? "", baseClose - 2 * HOUR_MS, Math.min(now, last + HOUR_MS), now);
|
|
163
|
+
if (!read || read.resolution !== "1h") return;
|
|
164
|
+
// Each finished hourly bar's close by the time it closed; a bar still forming has no close yet.
|
|
165
|
+
const closes = new Map<number, number>();
|
|
166
|
+
for (const bar of read.points) {
|
|
167
|
+
const open = new Date(bar.date).getTime();
|
|
168
|
+
if (!bar.partial && Number.isFinite(open) && Number.isFinite(bar.close) && bar.close > 0) closes.set(open + HOUR_MS, bar.close);
|
|
169
|
+
}
|
|
170
|
+
// Every day or none: a line of closes at the venue's close with a few UTC closes between would be neither.
|
|
171
|
+
if (!closes.has(baseClose) || times.some((time) => !closes.has(time))) return;
|
|
172
|
+
const points = entry.points.map((point, index) => {
|
|
173
|
+
const price = instants.has(index) ? closes.get(instants.get(index)!)! : undefined;
|
|
174
|
+
return price === undefined ? point : { ...point, value: price, close: price, rawValue: price };
|
|
175
|
+
});
|
|
176
|
+
next[next.indexOf(entry)] = { ...entry, points };
|
|
177
|
+
atSessionClose.add(id);
|
|
178
|
+
if (endTime !== null) closedEnds.add(id);
|
|
179
|
+
}));
|
|
180
|
+
return { timing: { venue, atSessionClose, closedEnds }, series: next };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* What a daily comparison of coins with listings that trade in sessions says
|
|
185
|
+
* of when each is measured: `BTC-USD at the US close` when its hourly bars
|
|
186
|
+
* put it there, else `BTC-USD to 00:00 UTC, 4 h after the US close`. Null
|
|
187
|
+
* when no coin is compared with such a listing.
|
|
188
|
+
*/
|
|
189
|
+
export function priceComparisonCloseTimes(
|
|
190
|
+
comparison: Pick<PriceComparison, "legs">,
|
|
191
|
+
series: readonly ResolvedSeries[],
|
|
192
|
+
labels: ReadonlyMap<string, string>,
|
|
193
|
+
atSessionClose: ReadonlySet<string> = new Set(),
|
|
194
|
+
): string | null {
|
|
195
|
+
const byId = new Map(series.map((entry) => [entry.id, entry]));
|
|
196
|
+
const compared = Object.keys(comparison.legs ?? {});
|
|
197
|
+
const venue = compared.map((id) => sessionVenueOf(byId.get(id))).find((entry): entry is string => !!entry);
|
|
198
|
+
const coins = compared.filter((id) => isRoundTheClockLeg(byId.get(id)));
|
|
199
|
+
if (!venue || coins.length === 0) return null;
|
|
200
|
+
const market = sessionMarketName(venue);
|
|
201
|
+
const parts = coins.map((id) => {
|
|
202
|
+
const label = labels.get(id) ?? id;
|
|
203
|
+
if (atSessionClose.has(id)) return `${label} at the ${market} close`;
|
|
204
|
+
const leg = comparison.legs![id]!;
|
|
205
|
+
const sessionClose = leg.intraday ? null : regularSessionCloseOn(venue, leg.endDay);
|
|
206
|
+
const coinClose = Date.parse(`${leg.endDay}T00:00:00Z`) + DAY_MS;
|
|
207
|
+
const hours = sessionClose === null ? null : Math.round((coinClose - sessionClose) / HOUR_MS);
|
|
208
|
+
return hours !== null && hours > 0 ? `${label} to 00:00 UTC, ${hours} h after the ${market} close` : `${label} to 00:00 UTC`;
|
|
209
|
+
});
|
|
210
|
+
return parts.join("; ");
|
|
211
|
+
}
|
|
@@ -54,6 +54,8 @@ interface HistoryAnchor {
|
|
|
54
54
|
/** Identity of the loaded version of the history's newest bar. */
|
|
55
55
|
key: string;
|
|
56
56
|
time: number;
|
|
57
|
+
/** The session's open, from a quote whose session is the whole bar (quoteSessionRange). */
|
|
58
|
+
open: number | null;
|
|
57
59
|
/** Live extremes and close observed while this bar is still forming. */
|
|
58
60
|
high: number | null;
|
|
59
61
|
low: number | null;
|
|
@@ -211,6 +213,7 @@ export class LiveBarAccumulator {
|
|
|
211
213
|
this.anchor = {
|
|
212
214
|
key,
|
|
213
215
|
time: latestTime,
|
|
216
|
+
open: forming ? previous?.open ?? null : null,
|
|
214
217
|
high: carried?.high ?? null,
|
|
215
218
|
low: carried?.low ?? null,
|
|
216
219
|
close: carried?.close ?? null,
|
|
@@ -316,6 +319,7 @@ export class LiveBarAccumulator {
|
|
|
316
319
|
return;
|
|
317
320
|
}
|
|
318
321
|
if (!forming && this.inBar(time, anchor.time, options, exchange)) {
|
|
322
|
+
if (range?.open !== undefined) anchor.open = range.open;
|
|
319
323
|
anchor.high = Math.max(anchor.high ?? price, price, range?.high ?? price);
|
|
320
324
|
anchor.low = Math.min(anchor.low ?? price, price, range?.low ?? price);
|
|
321
325
|
anchor.close = price;
|
|
@@ -364,8 +368,15 @@ export class LiveBarAccumulator {
|
|
|
364
368
|
const anchor = this.anchor!;
|
|
365
369
|
if (anchor.point?.source === latest && anchor.point.version === this.version) return anchor.point.value;
|
|
366
370
|
let value = latest;
|
|
367
|
-
|
|
368
|
-
|
|
371
|
+
if (pricePointIntegrity(latest)) {
|
|
372
|
+
// A later quote cannot establish which reported OHLC field was wrong, unless it names the bar's whole session:
|
|
373
|
+
// its open, range, price and count are the bar (mergeQuoteIntoLatestBar).
|
|
374
|
+
if (anchor.open !== null && anchor.close !== null) {
|
|
375
|
+
const close = anchor.close;
|
|
376
|
+
value = { ...latest, open: anchor.open, high: Math.max(anchor.high ?? close, anchor.open, close),
|
|
377
|
+
low: Math.min(anchor.low ?? close, anchor.open, close), close, ...this.cumulative !== null ? { volume: this.cumulative } : {} };
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
369
380
|
let volume = latest.volume;
|
|
370
381
|
const end = anchor.volumeEnd ?? this.cumulative;
|
|
371
382
|
if (isFiniteNumber(volume) && end !== null && anchor.volumeBase !== null) {
|
|
@@ -3,7 +3,7 @@ import { canonicalTimeSeriesFieldId, isMarketFieldId } from "./field-catalog";
|
|
|
3
3
|
import type { QuoteSubscriptionTarget } from "../types/data-provider";
|
|
4
4
|
import type { Quote } from "../types/financials";
|
|
5
5
|
import type { ChartSeriesSpec, ChartSpec, SecuritySeriesSource } from "./types";
|
|
6
|
-
import { activeStudyInputSeriesIds } from "./studies";
|
|
6
|
+
import { activeStudyInputSeriesIds } from "./studies/resolve";
|
|
7
7
|
import { valuationSeriesUsesLiveQuote } from "./fundamentals";
|
|
8
8
|
import { hasValidQuoteObservationTime } from "../market-data/quotes/freshness";
|
|
9
9
|
import type { InstrumentRef } from "../market-data/request-types";
|