@gloom-sh/terminal 0.18.5 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +140 -7
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +17 -6
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/index.ts +11 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +1 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/research-activity.ts +3 -2
- package/src/api-client/types.ts +70 -2
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +0 -2
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/pane-tabs-allowlist.json +3 -0
- package/src/architecture/ttl-caches-allowlist.json +19 -0
- package/src/brokers/sync-broker-instance.ts +2 -8
- package/src/capabilities/chart-series.ts +1 -1
- package/src/cli/commands/crypto-hints.ts +40 -24
- package/src/cli/commands/market.ts +72 -26
- package/src/cli/commands/options-chain.ts +48 -7
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +61 -12
- package/src/cli/commands/search.ts +21 -1
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +55 -34
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +8 -0
- package/src/cli/glossary.ts +25 -2
- package/src/cli/history-rows.ts +57 -9
- package/src/cli/index.ts +2 -0
- package/src/cli/listing-arg.ts +180 -52
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +3 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +86 -3
- package/src/cli/pane-functions/function-help.ts +13 -10
- package/src/cli/pane-functions/headless.ts +17 -7
- package/src/cli/pane-functions/index.ts +49 -14
- package/src/cli/pane-functions/market-session.ts +66 -4
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/resolver.ts +19 -8
- package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
- package/src/cli/pane-functions/screenshot-types.ts +5 -0
- package/src/cli/quote-answer.ts +22 -11
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +4 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-surface.tsx +52 -3
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +39 -4
- package/src/components/chart/composite/series-cues.ts +14 -0
- package/src/components/chart/composite/text-renderer.ts +9 -2
- package/src/components/chart/composite/tool-catalog.ts +36 -0
- package/src/components/chart/composite/tools.ts +203 -44
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
- package/src/components/command-bar/assist/inventory.ts +2 -1
- package/src/components/command-bar/commands/direct/index.ts +28 -13
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
- package/src/components/onboarding/wizard-broker-sync.ts +0 -1
- package/src/components/onboarding/wizard-positions.ts +3 -3
- package/src/components/ticker/list-table-view.tsx +4 -1
- package/src/components/ui/choice-dialog.tsx +23 -18
- package/src/components/ui/choice-groups.ts +18 -0
- package/src/components/ui/data-table/opentui/index.tsx +9 -7
- package/src/components/ui/data-table/opentui/model.ts +26 -1
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/list-view.tsx +8 -4
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +1 -0
- package/src/components/ui/select-button.tsx +5 -1
- package/src/components/ui/select-field.tsx +1 -0
- package/src/components/ui/table-layout.ts +8 -5
- package/src/core/app-runtime.ts +2 -0
- package/src/core/app-services.ts +2 -2
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/ticker-repository.ts +16 -9
- package/src/data/ticker-writes.ts +81 -0
- package/src/data/ttl-cache.ts +165 -0
- package/src/docs/snapshot.json +71 -137
- 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/market-data/depositary-pair.ts +2 -2
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +16 -3
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/options-alternatives.ts +81 -4
- package/src/news/aggregator.ts +113 -9
- package/src/news/cloud-session.ts +21 -0
- package/src/news/hooks.ts +1 -0
- package/src/news/news-model.ts +1 -0
- package/src/news/types.ts +11 -0
- package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +336 -77
- package/src/plugins/builtin/alerts/command.ts +25 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- package/src/plugins/builtin/alerts/fired-log.ts +73 -9
- package/src/plugins/builtin/alerts/format.ts +75 -5
- package/src/plugins/builtin/alerts/headless.ts +24 -6
- package/src/plugins/builtin/alerts/history-rows.ts +3 -2
- package/src/plugins/builtin/alerts/history.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +79 -16
- package/src/plugins/builtin/alerts/metric-command.ts +77 -4
- package/src/plugins/builtin/alerts/metric-data.ts +54 -13
- package/src/plugins/builtin/alerts/metric-model.ts +163 -14
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
- package/src/plugins/builtin/alerts/pane.tsx +61 -23
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +12 -8
- package/src/plugins/builtin/alerts/types.ts +4 -0
- package/src/plugins/builtin/analytics/index.tsx +2 -1
- package/src/plugins/builtin/analytics/risk-client.ts +16 -5
- package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
- package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
- package/src/plugins/builtin/analytics/risk-model.ts +55 -9
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/attention/index.tsx +2 -1
- package/src/plugins/builtin/attention/listing.ts +14 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/buildout/cells.tsx +12 -4
- package/src/plugins/builtin/buildout/format.ts +38 -0
- package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
- package/src/plugins/builtin/buildout/table-model.ts +23 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
- package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
- package/src/plugins/builtin/chart-composer/headless.ts +6 -1
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
- package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
- package/src/plugins/builtin/clinical-trials/model.ts +0 -9
- package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/company-attention/building.tsx +5 -1
- package/src/plugins/builtin/company-attention/client.ts +8 -9
- package/src/plugins/builtin/company-attention/evidence.ts +4 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +9 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/correlation/headless.ts +13 -5
- package/src/plugins/builtin/correlation/history.ts +22 -0
- package/src/plugins/builtin/cot/client.ts +22 -18
- package/src/plugins/builtin/cot/headless.ts +15 -10
- package/src/plugins/builtin/cot/model.ts +14 -1
- package/src/plugins/builtin/cot/pane.tsx +22 -14
- package/src/plugins/builtin/credit-boards/client.ts +6 -10
- package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
- package/src/plugins/builtin/credit-conditions/model.ts +4 -0
- package/src/plugins/builtin/credit-conditions/series.ts +3 -1
- package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
- package/src/plugins/builtin/credit-documents/export.ts +173 -0
- package/src/plugins/builtin/credit-documents/headless.ts +7 -7
- package/src/plugins/builtin/credit-documents/model.ts +104 -14
- package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
- package/src/plugins/builtin/custom-view/sources.ts +2 -0
- package/src/plugins/builtin/dividend-yield/client.ts +131 -23
- package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
- package/src/plugins/builtin/dividend-yield/model.ts +8 -5
- package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
- package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
- package/src/plugins/builtin/dividend-yield/types.ts +20 -2
- package/src/plugins/builtin/dividend-yield/view.ts +30 -2
- package/src/plugins/builtin/docs/targets.ts +5 -1
- package/src/plugins/builtin/earnings/board-model.ts +16 -15
- package/src/plugins/builtin/earnings/board.tsx +62 -71
- package/src/plugins/builtin/earnings/headless.ts +40 -4
- package/src/plugins/builtin/earnings/scope.ts +31 -43
- package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
- package/src/plugins/builtin/econ/types.ts +2 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +6 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +36 -9
- package/src/plugins/builtin/equity-screener/categories.ts +179 -0
- package/src/plugins/builtin/equity-screener/client.ts +4 -2
- package/src/plugins/builtin/equity-screener/headless.ts +144 -13
- package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
- package/src/plugins/builtin/equity-screener/model.ts +99 -10
- package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
- package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
- package/src/plugins/builtin/fx-matrix/client.ts +6 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
- package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
- package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
- package/src/plugins/builtin/gpu/headless.ts +8 -2
- package/src/plugins/builtin/help/function-index.ts +4 -1
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +18 -1
- package/src/plugins/builtin/iv-history/client.ts +52 -11
- package/src/plugins/builtin/iv-history/format.ts +36 -23
- package/src/plugins/builtin/iv-history/headless.ts +33 -18
- package/src/plugins/builtin/iv-history/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/model.ts +163 -27
- package/src/plugins/builtin/iv-history/pane.tsx +10 -6
- package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
- package/src/plugins/builtin/iv-history/universe.ts +5 -1
- package/src/plugins/builtin/jobs/client.ts +25 -29
- package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
- package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
- package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
- package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
- package/src/plugins/builtin/macro-day/release-table.ts +2 -2
- package/src/plugins/builtin/market-heatmap/data.ts +8 -33
- package/src/plugins/builtin/market-movers/client.ts +25 -7
- package/src/plugins/builtin/market-movers/headless.ts +32 -12
- package/src/plugins/builtin/market-movers/index.tsx +26 -14
- package/src/plugins/builtin/market-movers/min-float.ts +74 -0
- package/src/plugins/builtin/market-movers/model.ts +4 -1
- package/src/plugins/builtin/market-movers/screener.ts +14 -0
- package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
- package/src/plugins/builtin/market-movers/session-table.ts +4 -3
- package/src/plugins/builtin/market-movers/table.ts +17 -5
- package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
- package/src/plugins/builtin/news/index.tsx +1 -0
- package/src/plugins/builtin/news/wire/categories.ts +19 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
- package/src/plugins/builtin/openfda/client.ts +14 -2
- package/src/plugins/builtin/openfda/index.tsx +3 -2
- package/src/plugins/builtin/openfda/model.ts +9 -0
- package/src/plugins/builtin/openfda/pane.tsx +42 -6
- package/src/plugins/builtin/options/analytics.ts +29 -5
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/table.ts +5 -2
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +17 -4
- package/src/plugins/builtin/options-positioning/client.ts +11 -0
- package/src/plugins/builtin/options-positioning/headless.ts +21 -8
- package/src/plugins/builtin/options-positioning/index.tsx +1 -1
- package/src/plugins/builtin/options-positioning/model.ts +78 -9
- package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
- package/src/plugins/builtin/options-scenario/client.ts +116 -47
- package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
- package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
- package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
- package/src/plugins/builtin/options-scenario/csp.ts +8 -2
- package/src/plugins/builtin/options-scenario/index.tsx +9 -7
- package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
- package/src/plugins/builtin/pe-band/model.ts +12 -49
- package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
- package/src/plugins/builtin/perps/cascade.ts +236 -0
- package/src/plugins/builtin/perps/client.ts +56 -2
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +19 -22
- package/src/plugins/builtin/perps/status-line.ts +25 -0
- package/src/plugins/builtin/plugin-module.ts +5 -1
- package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/client.ts +18 -4
- package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
- package/src/plugins/builtin/relative-rotation/model.ts +16 -40
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
- package/src/plugins/builtin/research/event-model.ts +10 -8
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +7 -5
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
- package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
- package/src/plugins/builtin/scanner/flow-model.ts +50 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
- package/src/plugins/builtin/scanner/flow-session.ts +146 -0
- package/src/plugins/builtin/scanner/headless.ts +204 -35
- package/src/plugins/builtin/scanner/index.ts +16 -5
- package/src/plugins/builtin/scanner/live-headless.ts +27 -3
- package/src/plugins/builtin/scanner/live-model.ts +11 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/headless.ts +10 -1
- package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
- package/src/plugins/builtin/sec/fund-facts.ts +116 -34
- package/src/plugins/builtin/sec/headless.ts +2 -1
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
- package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
- package/src/plugins/builtin/shared/earnings-report.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/members-menu.tsx +3 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
- package/src/plugins/builtin/shared/report-freshness.ts +3 -2
- package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
- package/src/plugins/builtin/shared/ticker-company.ts +77 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/model.ts +16 -7
- package/src/plugins/builtin/themes/pane.tsx +5 -5
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
- package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
- package/src/plugins/builtin/time-sales/pane.tsx +31 -19
- package/src/plugins/builtin/treasury-daily/client.ts +41 -5
- package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
- package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
- package/src/plugins/builtin/treasury-daily/model.ts +50 -3
- package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
- package/src/plugins/builtin/vol-surface/client.ts +3 -13
- package/src/plugins/builtin/vol-surface/headless.ts +3 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +2 -2
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/remote/resources.ts +2 -1
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/list-view.tsx +13 -10
- package/src/renderers/dom/desktop/query-bar.tsx +4 -2
- package/src/renderers/dom/desktop/select-field.tsx +4 -2
- package/src/renderers/dom/host/chart-surface.tsx +21 -0
- package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
- package/src/renderers/electrobun/bun/desktop/backend.ts +12 -0
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/shared/app-name.ts +13 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +3 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/series/registry.ts +1 -0
- package/src/series/types.ts +6 -0
- package/src/series/validate.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +1 -0
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/index.ts +8 -5
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/controller.ts +4 -3
- package/src/sync/core-contributors.ts +90 -31
- package/src/telemetry/stall-reports-dom.ts +38 -0
- package/src/telemetry/stall-reports-node.ts +75 -0
- package/src/telemetry/stall-reports.ts +214 -0
- package/src/test-support/preload.ts +14 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name.ts +44 -3
- package/src/tickers/listing-venues.ts +15 -3
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +1 -1
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/fundamentals.ts +44 -1
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/resolve-context.ts +3 -1
- package/src/time-series/resolve.ts +56 -4
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +3 -2
- package/src/time-series/studies.ts +8 -2
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +2 -3
- package/src/time-series/types.ts +22 -1
- package/src/types/financials.ts +32 -1
- package/src/types/headless.ts +21 -3
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +9 -0
- package/src/ui/host.tsx +3 -0
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +44 -0
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +24 -3
- package/src/utils/fx-market-hours.ts +26 -3
- package/src/utils/main-thread-stall.ts +35 -2
- package/src/utils/market-capitalization.ts +24 -1
- package/src/utils/price-history.ts +7 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import type { StallReportState, StallReporterHost } from "./stall-reports";
|
|
4
|
+
|
|
5
|
+
/** Next to config.json: one line per stall, newest last. */
|
|
6
|
+
const STALL_LOG_FILE = "stall-log.txt";
|
|
7
|
+
/** Next to config.json: when each kind of stall was last reported, and the counts since. */
|
|
8
|
+
const STATE_FILE = "stall-reports.json";
|
|
9
|
+
/** Past this the log keeps its newer half. */
|
|
10
|
+
const LOG_MAX_BYTES = 256 * 1024;
|
|
11
|
+
|
|
12
|
+
function readCounts(value: unknown): StallReportState["pending"] {
|
|
13
|
+
const pending: StallReportState["pending"] = {};
|
|
14
|
+
if (!value || typeof value !== "object") return pending;
|
|
15
|
+
for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {
|
|
16
|
+
const { count, longestMs } = (entry ?? {}) as Record<string, unknown>;
|
|
17
|
+
if (typeof count === "number" && Number.isFinite(count) && typeof longestMs === "number" && Number.isFinite(longestMs)) {
|
|
18
|
+
pending[key] = { count, longestMs };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return pending;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The saved state, keeping only entries of the right shape. */
|
|
25
|
+
function parseStallReportState(text: string): StallReportState {
|
|
26
|
+
const parsed: unknown = JSON.parse(text);
|
|
27
|
+
const record = parsed && typeof parsed === "object" ? parsed as Record<string, unknown> : {};
|
|
28
|
+
const sent: Record<string, number> = {};
|
|
29
|
+
if (record.sent && typeof record.sent === "object") {
|
|
30
|
+
for (const [key, at] of Object.entries(record.sent as Record<string, unknown>)) {
|
|
31
|
+
if (typeof at === "number" && Number.isFinite(at)) sent[key] = at;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { sent, pending: readCounts(record.pending) };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function trimLog(path: string): void {
|
|
38
|
+
if (statSync(path).size <= LOG_MAX_BYTES) return;
|
|
39
|
+
const text = readFileSync(path, "utf-8");
|
|
40
|
+
const keep = text.slice(text.length - LOG_MAX_BYTES / 2);
|
|
41
|
+
writeFileSync(path, keep.slice(keep.indexOf("\n") + 1), "utf-8");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The stall reporter for a Bun process (the terminal app, the desktop app's main process). */
|
|
45
|
+
export function createNodeStallReporterHost(options: {
|
|
46
|
+
/** The profile's data folder, or null before the config is loaded. */
|
|
47
|
+
getDataDir(): string | null;
|
|
48
|
+
}): StallReporterHost {
|
|
49
|
+
const pathFor = (file: string) => {
|
|
50
|
+
const dir = options.getDataDir();
|
|
51
|
+
return dir ? join(dir, file) : null;
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
platform: process.platform,
|
|
55
|
+
arch: process.arch,
|
|
56
|
+
appendLog: (line) => {
|
|
57
|
+
const path = pathFor(STALL_LOG_FILE);
|
|
58
|
+
if (!path) return;
|
|
59
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
60
|
+
appendFileSync(path, `${line}\n`, "utf-8");
|
|
61
|
+
trimLog(path);
|
|
62
|
+
},
|
|
63
|
+
load: () => {
|
|
64
|
+
const path = pathFor(STATE_FILE);
|
|
65
|
+
if (!path || !existsSync(path)) return { sent: {}, pending: {} };
|
|
66
|
+
return parseStallReportState(readFileSync(path, "utf-8"));
|
|
67
|
+
},
|
|
68
|
+
save: (state) => {
|
|
69
|
+
const path = pathFor(STATE_FILE);
|
|
70
|
+
if (!path) return;
|
|
71
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
72
|
+
writeFileSync(path, JSON.stringify(state), "utf-8");
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { logFailure } from "../utils/log-failure";
|
|
2
|
+
import type { StallEvent } from "../utils/main-thread-stall";
|
|
3
|
+
import { VERSION } from "../version";
|
|
4
|
+
import { reportCrash } from "./crash-reports";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Freezes, made visible outside the app's own Debug Log.
|
|
8
|
+
*
|
|
9
|
+
* The stall monitor (utils/main-thread-stall.ts) notices when a process could
|
|
10
|
+
* not answer the user for a while and names the last measured section, and
|
|
11
|
+
* hands the stall to its sink. The terminal app and the desktop app's Bun
|
|
12
|
+
* process record their own stalls here; the desktop window sends the stalls
|
|
13
|
+
* it had in front of the user (stall-reports-dom.ts) to the Bun process,
|
|
14
|
+
* which records them too. Where a host is installed, each
|
|
15
|
+
* stall:
|
|
16
|
+
*
|
|
17
|
+
* - goes to a plain-text stall log in the profile folder, one line each, so a
|
|
18
|
+
* user or support can read what froze and when;
|
|
19
|
+
* - from a second up, counts toward a report sent through the crash reporter
|
|
20
|
+
* (and so behind the same off switch), the way update failures are: the
|
|
21
|
+
* duration bucket, the section's fixed name, the process, the longest
|
|
22
|
+
* stall and how many there were. No free text: a section name keeps only
|
|
23
|
+
* its fixed leading part, so a ticker in a queue key never leaves.
|
|
24
|
+
*
|
|
25
|
+
* One report per process, bucket, section and app version a day; stalls of
|
|
26
|
+
* the same kind after that are counted and carried in the next report.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** Which process stalled. */
|
|
30
|
+
export type StallProcess = "window" | "app" | "terminal";
|
|
31
|
+
|
|
32
|
+
/** What error tracking gets: fixed values only. */
|
|
33
|
+
export interface StallReport {
|
|
34
|
+
process: StallProcess;
|
|
35
|
+
bucket: StallBucket;
|
|
36
|
+
section: string;
|
|
37
|
+
appVersion: string;
|
|
38
|
+
platform: "macos" | "windows" | "linux" | "other";
|
|
39
|
+
arch: "arm64" | "x64" | "other";
|
|
40
|
+
/** Stalls of this kind since the last report, this one included. */
|
|
41
|
+
count: number;
|
|
42
|
+
/** The longest of them, rounded to 100 ms. */
|
|
43
|
+
longestMs: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type StallBucket = "1-2s" | "2-5s" | "5-15s" | "15s+";
|
|
47
|
+
|
|
48
|
+
/** What the host keeps on disk between launches. */
|
|
49
|
+
export interface StallReportState {
|
|
50
|
+
/** When each kind was last reported, in ms. */
|
|
51
|
+
sent: Record<string, number>;
|
|
52
|
+
/** Stalls counted since each kind was last reported. */
|
|
53
|
+
pending: Record<string, { count: number; longestMs: number }>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface StallReporterHost {
|
|
57
|
+
/** The running version; defaults to this build's. */
|
|
58
|
+
appVersion?: string;
|
|
59
|
+
platform: string;
|
|
60
|
+
arch: string;
|
|
61
|
+
/** Appends one line to the stall log. */
|
|
62
|
+
appendLog(line: string): void;
|
|
63
|
+
load(): StallReportState;
|
|
64
|
+
save(state: StallReportState): void;
|
|
65
|
+
now?(): number;
|
|
66
|
+
/** Defaults to the crash reporter. */
|
|
67
|
+
send?(report: StallReport): void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
71
|
+
/** Shorter freezes go to the log only. */
|
|
72
|
+
const STALL_REPORT_MIN_MS = 1_000;
|
|
73
|
+
const SECTION_PATTERN = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*/;
|
|
74
|
+
const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]{1,60})?$/;
|
|
75
|
+
/** Kinds with counts waiting for their next report; the oldest go first past this. */
|
|
76
|
+
const MAX_KINDS_KEPT = 200;
|
|
77
|
+
|
|
78
|
+
let host: StallReporterHost | null = null;
|
|
79
|
+
|
|
80
|
+
/** The fixed part of a section name: lower-case dotted words, cut where anything else starts. */
|
|
81
|
+
function stallSectionName(name: string | undefined): string {
|
|
82
|
+
if (!name) return "unmeasured";
|
|
83
|
+
const match = SECTION_PATTERN.exec(name);
|
|
84
|
+
const section = match?.[0].replace(/\.$/, "");
|
|
85
|
+
return section && section.length <= 80 ? section : "unmeasured";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function stallBucket(blockedMs: number): StallBucket {
|
|
89
|
+
if (blockedMs < 2_000) return "1-2s";
|
|
90
|
+
if (blockedMs < 5_000) return "2-5s";
|
|
91
|
+
if (blockedMs < 15_000) return "5-15s";
|
|
92
|
+
return "15s+";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A stall another process sent over, kept only when it has the right shape. */
|
|
96
|
+
export function parseStallEvent(value: unknown): StallEvent | null {
|
|
97
|
+
if (!value || typeof value !== "object") return null;
|
|
98
|
+
const { blockedMs, lastMeasured, lastMeasuredEndedMsAgo } = value as Record<string, unknown>;
|
|
99
|
+
if (typeof blockedMs !== "number" || !Number.isFinite(blockedMs) || blockedMs <= 0) return null;
|
|
100
|
+
return {
|
|
101
|
+
blockedMs,
|
|
102
|
+
...(typeof lastMeasured === "string" && lastMeasured.length <= 200 ? { lastMeasured } : {}),
|
|
103
|
+
...(typeof lastMeasuredEndedMsAgo === "number" && Number.isFinite(lastMeasuredEndedMsAgo) ? { lastMeasuredEndedMsAgo } : {}),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function formatSeconds(ms: number): string {
|
|
108
|
+
return `${(ms / 1000).toFixed(ms < 10_000 ? 1 : 0)} s`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** One line of the stall log: when, which process, how long, and what ran last. */
|
|
112
|
+
function formatStallLogLine(process: StallProcess, event: StallEvent, at: number): string {
|
|
113
|
+
const time = new Date(at).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
114
|
+
const last = event.lastMeasured
|
|
115
|
+
? `last measured: ${event.lastMeasured}${event.lastMeasuredEndedMsAgo === undefined ? "" : `, ended ${Math.round(event.lastMeasuredEndedMsAgo)} ms before`}`
|
|
116
|
+
: "nothing measured before it";
|
|
117
|
+
return `${time} ${process.padEnd(8)} froze ${formatSeconds(event.blockedMs).padStart(6)} ${last}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function reportPlatform(platform: string): StallReport["platform"] {
|
|
121
|
+
if (platform === "darwin") return "macos";
|
|
122
|
+
if (platform === "win32") return "windows";
|
|
123
|
+
if (platform === "linux") return "linux";
|
|
124
|
+
return "other";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function reportArch(arch: string): StallReport["arch"] {
|
|
128
|
+
return arch === "arm64" || arch === "x64" ? arch : "other";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function sendThroughCrashReporter(report: StallReport): void {
|
|
132
|
+
const details = [
|
|
133
|
+
`process=${report.process}`,
|
|
134
|
+
`count=${report.count}`,
|
|
135
|
+
`longest=${report.longestMs}ms`,
|
|
136
|
+
`platform=${report.platform}-${report.arch}`,
|
|
137
|
+
].join(" ");
|
|
138
|
+
// A plain object, not an Error: it has no stack, so no file path goes along.
|
|
139
|
+
// The message is the bucket and section, so error tracking keeps one issue per kind.
|
|
140
|
+
reportCrash(
|
|
141
|
+
{ name: "MainThreadStall", message: `${report.bucket} after ${report.section}` },
|
|
142
|
+
{ kind: "plugin", plugin: "stall-monitor", pane: details },
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function loadState(current: StallReporterHost): StallReportState {
|
|
147
|
+
try {
|
|
148
|
+
const state = current.load();
|
|
149
|
+
return { sent: { ...state.sent }, pending: { ...state.pending } };
|
|
150
|
+
} catch (error) {
|
|
151
|
+
logFailure("telemetry-stalls", "Could not read stall report state")(error);
|
|
152
|
+
return { sent: {}, pending: {} };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function prune(state: StallReportState, now: number): void {
|
|
157
|
+
for (const [key, at] of Object.entries(state.sent)) {
|
|
158
|
+
if (!(now - at < DAY_MS) || at > now) delete state.sent[key];
|
|
159
|
+
}
|
|
160
|
+
const kinds = Object.keys(state.pending);
|
|
161
|
+
for (const key of kinds.slice(0, Math.max(0, kinds.length - MAX_KINDS_KEPT))) delete state.pending[key];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Records one stall of `process`. Does nothing where no host is installed; never throws. */
|
|
165
|
+
export function recordStall(process: StallProcess, event: StallEvent): void {
|
|
166
|
+
const current = host;
|
|
167
|
+
if (!current || !(event.blockedMs > 0)) return;
|
|
168
|
+
try {
|
|
169
|
+
const now = current.now?.() ?? Date.now();
|
|
170
|
+
current.appendLog(formatStallLogLine(process, event, now));
|
|
171
|
+
if (event.blockedMs < STALL_REPORT_MIN_MS) return;
|
|
172
|
+
const appVersion = (current.appVersion ?? VERSION).trim().replace(/^v/, "");
|
|
173
|
+
const section = stallSectionName(event.lastMeasured);
|
|
174
|
+
const bucket = stallBucket(event.blockedMs);
|
|
175
|
+
const version = VERSION_PATTERN.test(appVersion) ? appVersion : "unknown";
|
|
176
|
+
const key = `${process}|${bucket}|${section}|${version}`;
|
|
177
|
+
const state = loadState(current);
|
|
178
|
+
prune(state, now);
|
|
179
|
+
const pending = state.pending[key] ?? { count: 0, longestMs: 0 };
|
|
180
|
+
pending.count += 1;
|
|
181
|
+
pending.longestMs = Math.max(pending.longestMs, Math.round(event.blockedMs / 100) * 100);
|
|
182
|
+
if (state.sent[key] !== undefined) {
|
|
183
|
+
state.pending[key] = pending;
|
|
184
|
+
} else {
|
|
185
|
+
delete state.pending[key];
|
|
186
|
+
state.sent[key] = now;
|
|
187
|
+
(current.send ?? sendThroughCrashReporter)({
|
|
188
|
+
process,
|
|
189
|
+
bucket,
|
|
190
|
+
section,
|
|
191
|
+
appVersion: version,
|
|
192
|
+
platform: reportPlatform(current.platform),
|
|
193
|
+
arch: reportArch(current.arch),
|
|
194
|
+
count: pending.count,
|
|
195
|
+
longestMs: pending.longestMs,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
current.save(state);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
logFailure("telemetry-stalls", "Could not record a stall")(error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function installStallReporter(next: StallReporterHost): () => void {
|
|
205
|
+
host = next;
|
|
206
|
+
return () => {
|
|
207
|
+
if (host === next) host = null;
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Test seam. */
|
|
212
|
+
export function resetStallReporterForTests(): void {
|
|
213
|
+
host = null;
|
|
214
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { beforeEach } from "bun:test";
|
|
2
|
+
import { resetCrashReporterForTests } from "../telemetry/crash-reports";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `bun test` runs every file in one process, so module state outlives the file
|
|
6
|
+
* that set it. A crash report queued while no reporter is installed, such as a
|
|
7
|
+
* plugin that fails to register in an app runtime test, would wait in the
|
|
8
|
+
* queue and go out with the next reporter another file installs, and its key
|
|
9
|
+
* would keep a later identical report from being queued at all. Every test
|
|
10
|
+
* starts with no reporter, an empty queue and nothing seen.
|
|
11
|
+
*/
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
resetCrashReporterForTests();
|
|
14
|
+
});
|
package/src/theme/themes.ts
CHANGED
|
@@ -745,6 +745,16 @@ const THEME_TRAITS: Readonly<Record<string, readonly ThemeTrait[]>> = {
|
|
|
745
745
|
"high-contrast": ["high-contrast", "colorblind-safe"],
|
|
746
746
|
};
|
|
747
747
|
|
|
748
|
+
/**
|
|
749
|
+
* Whether a theme's gains and losses are told apart by something other than
|
|
750
|
+
* the red and green everyone reads: a color-blind safe theme's blue and
|
|
751
|
+
* vermillion, which a chart keys (`▲▼` beside a candle series) so a reader
|
|
752
|
+
* knows which is which.
|
|
753
|
+
*/
|
|
754
|
+
export function themeKeysDirection(id: string): boolean {
|
|
755
|
+
return (THEME_TRAITS[id] ?? []).includes("colorblind-safe");
|
|
756
|
+
}
|
|
757
|
+
|
|
748
758
|
export interface ThemeSummary {
|
|
749
759
|
id: string;
|
|
750
760
|
name: string;
|
|
@@ -99,12 +99,53 @@ export function companyNameMatch(typed: string, name: string | null | undefined)
|
|
|
99
99
|
return { extra: words.size - matched.size, abbreviated };
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
// Words one fund's venues name it with or without: "iShares MSCI Japan Index Fund" in New York is "iShares MSCI Japan ETF" in Mexico.
|
|
103
|
+
const FUND_NOISE = new Set(["etf", "etfs", "fund", "funds", "index", "trust", "ucits"]);
|
|
104
|
+
|
|
105
|
+
function instrumentWords(name: string | null | undefined): Set<string> {
|
|
106
|
+
return new Set([...companyWords(name)].filter((word) => !FUND_NOISE.has(word)));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Whether two venues' names can be one instrument's: one company's
|
|
111
|
+
* (`sameCompany`), or one fund's however a venue words it, with or without
|
|
112
|
+
* "ETF", "Index Fund" or "Trust" ("Technology Select Sector SPDR Fund" in
|
|
113
|
+
* Buenos Aires is State Street's ETF in New York). Two names that are not are
|
|
114
|
+
* another company's or another fund's ("W.A.G Payment Solutions" is not the
|
|
115
|
+
* iShares fund, nor "UBS MSCI India" iShares' India fund). An empty name
|
|
116
|
+
* matches none.
|
|
117
|
+
*/
|
|
118
|
+
export function sameListedName(left: string | null | undefined, right: string | null | undefined): boolean {
|
|
119
|
+
if (sameCompany(left, right)) return true;
|
|
120
|
+
const a = instrumentWords(left);
|
|
121
|
+
const b = instrumentWords(right);
|
|
122
|
+
if (a.size === 0 || b.size === 0) return false;
|
|
123
|
+
const [fewer, more] = a.size <= b.size ? [a, b] : [b, a];
|
|
124
|
+
return [...fewer].every((word) => more.has(word));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Whether a venue names its line a depositary receipt ("Toyota Motor
|
|
129
|
+
* Corporation CEDEAR", "SS SPDR S&P 500 ETF Trust - CEDEAR"): a receipt of a
|
|
130
|
+
* line elsewhere, whose short form of that line's name says little of whose it is.
|
|
131
|
+
*/
|
|
132
|
+
export function namesDepositaryReceipt(name: string | null | undefined): boolean {
|
|
133
|
+
return /\b(?:cedears?|adrs?|ads|cdrs?|cdis?|depositary)\b/i.test(name ?? "");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Items grouped by the company their names are (`same`, `sameCompany` unless
|
|
138
|
+
* given), in the order each company first appears. Unnamed items are left out.
|
|
139
|
+
*/
|
|
140
|
+
export function groupByCompany<T>(
|
|
141
|
+
items: readonly T[],
|
|
142
|
+
nameOf: (item: T) => string | null | undefined,
|
|
143
|
+
same: (left: string | null | undefined, right: string | null | undefined) => boolean = sameCompany,
|
|
144
|
+
): T[][] {
|
|
104
145
|
const groups: T[][] = [];
|
|
105
146
|
for (const item of items) {
|
|
106
147
|
if (companyWords(nameOf(item)).size === 0) continue;
|
|
107
|
-
const group = groups.find((members) => members.some((member) =>
|
|
148
|
+
const group = groups.find((members) => members.some((member) => same(nameOf(member), nameOf(item))));
|
|
108
149
|
if (group) group.push(item);
|
|
109
150
|
else groups.push([item]);
|
|
110
151
|
}
|
|
@@ -28,6 +28,17 @@ export interface ListingVenue {
|
|
|
28
28
|
/** Canonical code. */
|
|
29
29
|
exchange: string;
|
|
30
30
|
name: string;
|
|
31
|
+
/**
|
|
32
|
+
* The listing's own symbol there, when search lists it under another
|
|
33
|
+
* spelling than the one asked for: BP for `BP.` (London's code for it).
|
|
34
|
+
*/
|
|
35
|
+
symbol?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The venue's own symbol, when it is not `asked` (ignoring case). */
|
|
39
|
+
function ownSymbol(listed: string | undefined, asked: string): { symbol: string } | Record<string, never> {
|
|
40
|
+
const symbol = listed?.trim().toUpperCase();
|
|
41
|
+
return symbol && symbol !== asked.trim().toUpperCase() ? { symbol } : {};
|
|
31
42
|
}
|
|
32
43
|
|
|
33
44
|
/**
|
|
@@ -50,14 +61,15 @@ export async function listingVenues(symbol: string, { store, dataProvider }: Lis
|
|
|
50
61
|
: {
|
|
51
62
|
exchange: resolved.result.exchange === "SMART" ? resolved.result.primaryExchange ?? "" : resolved.result.exchange || resolved.result.primaryExchange || "",
|
|
52
63
|
name: resolved.result.name,
|
|
64
|
+
...ownSymbol(parsePublicTickerKey(resolved.result.symbol).symbol, symbol),
|
|
53
65
|
};
|
|
54
|
-
venues = only?.exchange ? [{ exchange: canonicalExchange(only.exchange)
|
|
66
|
+
venues = only?.exchange ? [{ ...only, exchange: canonicalExchange(only.exchange) }] : [];
|
|
55
67
|
} catch (error) {
|
|
56
68
|
// One listing settles the picker; several are its choices.
|
|
57
69
|
if (!(error instanceof AmbiguousTickerError) || error instanceof AmbiguousContractError) return [];
|
|
58
70
|
venues = error.listings.flatMap((listing) => {
|
|
59
|
-
const
|
|
60
|
-
return exchange ? [{ exchange, name: error.listingNames[listing] ?? "" }] : [];
|
|
71
|
+
const key = parsePublicTickerKey(listing);
|
|
72
|
+
return key.exchange ? [{ exchange: key.exchange, name: error.listingNames[listing] ?? "", ...ownSymbol(key.symbol, symbol) }] : [];
|
|
61
73
|
});
|
|
62
74
|
}
|
|
63
75
|
return venues.filter((venue, index) => venues.findIndex((other) => other.exchange === venue.exchange) === index);
|
|
@@ -412,6 +412,25 @@ export interface NearMissListing {
|
|
|
412
412
|
symbol: string;
|
|
413
413
|
}
|
|
414
414
|
|
|
415
|
+
/**
|
|
416
|
+
* `BP.`, `RR.`: London's code for a company with a two-letter symbol, which
|
|
417
|
+
* the exchange writes with a dot to make three. No other venue writes a
|
|
418
|
+
* trailing dot, though one can trail a US ticker by slip (`GE.`).
|
|
419
|
+
*/
|
|
420
|
+
const LONDON_DOTTED = /^([A-Z]{2})\.$/;
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* A two-letter symbol written with London's trailing dot (`BP.`): what was
|
|
424
|
+
* typed and the symbol (BP). Whether it is London's line (BP:LSE) or a US
|
|
425
|
+
* ticker with a stray dot (`GE.`) is for where London lists it to say
|
|
426
|
+
* (`resolveCliListing`). Null for anything else.
|
|
427
|
+
*/
|
|
428
|
+
export function readLondonDottedSymbol(raw: string): NearMissListing | null {
|
|
429
|
+
const typed = raw.trim();
|
|
430
|
+
const london = LONDON_DOTTED.exec(typed.toUpperCase());
|
|
431
|
+
return london ? { typed, symbol: london[1]! } : null;
|
|
432
|
+
}
|
|
433
|
+
|
|
415
434
|
/** `BRK/B`: a US share class after a slash, as a broker writes it. One letter before the slash is a ratio or an abbreviation (P/E, N/A). */
|
|
416
435
|
const SLASH_CLASS = /^([A-Z]{2,5})\/([A-Z])$/;
|
|
417
436
|
/** `700.HK`: a Hong Kong code without its leading zeros. */
|
|
@@ -46,7 +46,7 @@ function isStrictlyOrdered(points: readonly TimeSeriesPoint[]): boolean {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/** Callers only read the result, so ordered input (every market series) is returned as is. */
|
|
49
|
-
function sortedUniquePoints(points: readonly TimeSeriesPoint[]): readonly TimeSeriesPoint[] {
|
|
49
|
+
export function sortedUniquePoints(points: readonly TimeSeriesPoint[]): readonly TimeSeriesPoint[] {
|
|
50
50
|
// Live charts re-resolve on every quote; skip the keyed sort when nothing needs it.
|
|
51
51
|
if (isStrictlyOrdered(points)) return points;
|
|
52
52
|
const byObservation = new Map<string, TimeSeriesPoint>();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TimeSeriesPoint } from "./types";
|
|
2
2
|
import type { CloudFredSeriesInfoPayload, CloudFredSeriesPayload } from "../api-client";
|
|
3
|
-
import { ICE_CREDIT_SERIES_IDS } from "./ice-credit";
|
|
3
|
+
import { ICE_CREDIT_SERIES_IDS, ICE_HISTORY_LIMIT_NOTICE } from "./ice-credit";
|
|
4
4
|
|
|
5
5
|
function sourceDate(value: string | undefined): number | null {
|
|
6
6
|
if (!value || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
|
|
@@ -8,6 +8,21 @@ function sourceDate(value: string | undefined): number | null {
|
|
|
8
8
|
return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === value ? time : null;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
const THREE_YEAR_RETENTION = /Starting in April 2026, this series will only include 3 years of observations\./;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The publisher's cap on how far back an ICE credit series goes, from the
|
|
15
|
+
* series' own notes, so it lapses if FRED lifts it. Null for any other series.
|
|
16
|
+
*/
|
|
17
|
+
export function fredHistoryLimitNotice(
|
|
18
|
+
seriesId: string,
|
|
19
|
+
info: Pick<CloudFredSeriesInfoPayload, "id" | "notes"> | null | undefined,
|
|
20
|
+
): string | null {
|
|
21
|
+
const id = seriesId.trim().toUpperCase();
|
|
22
|
+
if (!ICE_CREDIT_SERIES_IDS.has(id) || info?.id.trim().toUpperCase() !== id) return null;
|
|
23
|
+
return THREE_YEAR_RETENTION.test(info.notes) ? ICE_HISTORY_LIMIT_NOTICE : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
11
26
|
/** Source coverage is separate from query limits and calculation buffers. */
|
|
12
27
|
export function fredCreditCoverageNotice(
|
|
13
28
|
seriesId: string,
|
|
@@ -21,9 +36,8 @@ export function fredCreditCoverageNotice(
|
|
|
21
36
|
const end = sourceDate(info.observationEnd);
|
|
22
37
|
if (start === null || end === null || start > end) return null;
|
|
23
38
|
if (visibleStart !== null && visibleStart >= start) return null;
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
: "";
|
|
39
|
+
const limit = fredHistoryLimitNotice(id, info);
|
|
40
|
+
const retention = limit ? ` ${limit}` : "";
|
|
27
41
|
// Series metadata and observations can refresh independently. An observation
|
|
28
42
|
// outside the declared span disproves that span; the query's first/last row
|
|
29
43
|
// cannot establish replacement source coverage either.
|
|
@@ -32,7 +32,7 @@ const FIELDS = [
|
|
|
32
32
|
unit: "currency",
|
|
33
33
|
unitGroup: "price",
|
|
34
34
|
nativeFrequency: "daily",
|
|
35
|
-
styles: ["candles", "ohlc", "hlc", "line", "area"],
|
|
35
|
+
styles: ["candles", "hollow", "heikin-ashi", "ohlc", "hlc", "line", "area"],
|
|
36
36
|
defaultStyle: "candles",
|
|
37
37
|
transforms: MARKET_TRANSFORMS,
|
|
38
38
|
defaultInterpolation: "none",
|