@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
|
@@ -112,6 +112,21 @@ export function useOptionsSessionOpen(enabled = true): boolean {
|
|
|
112
112
|
return state.nextChangeAt != null && Date.now() >= state.nextChangeAt ? usOptionsSession(Date.now()).open : state.open;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
export interface LiveSessionRefresh {
|
|
116
|
+
/** The refresh cycle is running, which is what "live" means. */
|
|
117
|
+
live: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Why the last refresh failed, until one succeeds or the cycle stops. The
|
|
120
|
+
* data it would have replaced stays on screen, so the pane shows this where
|
|
121
|
+
* it shows its own failed refreshes.
|
|
122
|
+
*/
|
|
123
|
+
error: string | null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isCancellation(error: unknown): boolean {
|
|
127
|
+
return !!error && typeof error === "object" && (error as { name?: unknown }).name === "AbortError";
|
|
128
|
+
}
|
|
129
|
+
|
|
115
130
|
/**
|
|
116
131
|
* Calls `refresh` every `intervalMs` while enabled, the pane can be seen (the
|
|
117
132
|
* app is visible and the pane is not covered) and the options market is open.
|
|
@@ -119,18 +134,19 @@ export function useOptionsSessionOpen(enabled = true): boolean {
|
|
|
119
134
|
* after the caller's own load (`loadedAt`); returning after a hidden or
|
|
120
135
|
* covered stretch, or reaching the open, more than an interval after the last
|
|
121
136
|
* data fires straight away. A refresh still in flight is never overlapped by
|
|
122
|
-
* the next.
|
|
123
|
-
*
|
|
137
|
+
* the next. A refresh that throws or rejects is reported as `error` until the
|
|
138
|
+
* next one succeeds; a cancelled one (an AbortError) is no failure.
|
|
124
139
|
*/
|
|
125
140
|
export function useLiveSessionRefresh(
|
|
126
141
|
refresh: () => void | Promise<unknown>,
|
|
127
142
|
intervalMs: number,
|
|
128
143
|
enabled: boolean,
|
|
129
144
|
loadedAt?: number | null,
|
|
130
|
-
):
|
|
145
|
+
): LiveSessionRefresh {
|
|
131
146
|
const visible = usePaneVisible();
|
|
132
147
|
const open = useOptionsSessionOpen(enabled);
|
|
133
148
|
const active = enabled && visible && open;
|
|
149
|
+
const [error, setError] = useState<string | null>(null);
|
|
134
150
|
const refreshRef = useRef(refresh);
|
|
135
151
|
refreshRef.current = refresh;
|
|
136
152
|
const lastRef = useRef(Date.now());
|
|
@@ -142,21 +158,31 @@ export function useLiveSessionRefresh(
|
|
|
142
158
|
let cancelled = false;
|
|
143
159
|
// A load the caller finished itself counts as the latest refresh.
|
|
144
160
|
if (loadedAtRef.current != null && loadedAtRef.current > lastRef.current) lastRef.current = loadedAtRef.current;
|
|
161
|
+
const succeeded = () => { if (!cancelled) setError(null); };
|
|
162
|
+
const failed = (failure: unknown) => {
|
|
163
|
+
if (cancelled || isCancellation(failure)) return;
|
|
164
|
+
const message = (failure instanceof Error ? failure.message : String(failure ?? "")).trim();
|
|
165
|
+
setError(message || "Live refresh failed");
|
|
166
|
+
};
|
|
145
167
|
const run = () => {
|
|
146
168
|
if (inFlight || cancelled) return;
|
|
147
169
|
lastRef.current = Date.now();
|
|
148
170
|
let pending: void | Promise<unknown>;
|
|
149
|
-
try { pending = refreshRef.current(); } catch {
|
|
171
|
+
try { pending = refreshRef.current(); } catch (failure) {
|
|
172
|
+
failed(failure);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
150
175
|
if (pending && typeof (pending as Promise<unknown>).then === "function") {
|
|
151
176
|
inFlight = true;
|
|
152
|
-
void (pending as Promise<unknown>).
|
|
153
|
-
}
|
|
177
|
+
void (pending as Promise<unknown>).then(succeeded, failed).finally(() => { inFlight = false; });
|
|
178
|
+
} else succeeded();
|
|
154
179
|
};
|
|
155
180
|
if (Date.now() - lastRef.current >= intervalMs) run();
|
|
156
181
|
const timer = setInterval(run, intervalMs);
|
|
157
|
-
|
|
182
|
+
// A stopped cycle's failure is old news: the pane's own loads take over.
|
|
183
|
+
return () => { cancelled = true; clearInterval(timer); setError(null); };
|
|
158
184
|
}, [active, intervalMs]);
|
|
159
|
-
return active;
|
|
185
|
+
return { live: active, error: active ? error : null };
|
|
160
186
|
}
|
|
161
187
|
|
|
162
188
|
/**
|
|
@@ -134,6 +134,25 @@ export function valueOption(input: OptionPricingInput): OptionValuation {
|
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
/**
|
|
138
|
+
* The chance the underlying closes above `strike` at expiry under the same
|
|
139
|
+
* Black-Scholes-Merton model: N(d2), the risk-neutral probability a call there
|
|
140
|
+
* finishes in the money. 1 or 0 when nothing is left uncertain (expiry, zero
|
|
141
|
+
* volatility), by where the discounted forward sits.
|
|
142
|
+
*/
|
|
143
|
+
export function probabilityAbove(input: Omit<OptionPricingInput, "side">): number {
|
|
144
|
+
const spot = Math.max(0, finite(input.spot));
|
|
145
|
+
const strike = Math.max(0, finite(input.strike));
|
|
146
|
+
const years = Math.max(0, finite(input.daysToExpiry)) / DAYS_PER_YEAR;
|
|
147
|
+
const rate = finite(input.rate);
|
|
148
|
+
const dividendYield = finite(input.dividendYield);
|
|
149
|
+
const volatility = Math.max(0, finite(input.volatility));
|
|
150
|
+
if (strike <= 0) return 1;
|
|
151
|
+
if (years <= 0 || volatility <= 0 || spot <= 0) return spot * Math.exp((rate - dividendYield) * years) > strike ? 1 : 0;
|
|
152
|
+
const d2 = (Math.log(spot / strike) + (rate - dividendYield - volatility * volatility / 2) * years) / (volatility * Math.sqrt(years));
|
|
153
|
+
return normalCdf(d2);
|
|
154
|
+
}
|
|
155
|
+
|
|
137
156
|
export interface ImpliedVolatilityResult {
|
|
138
157
|
volatility: number | null;
|
|
139
158
|
/** Why no volatility could be solved, phrased for the pane body. */
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
2
|
+
|
|
1
3
|
type Arm = "control" | "teaser" | null;
|
|
2
4
|
type TeaserKind = "summary" | "sample" | "none";
|
|
3
5
|
const active = new Map<string, Set<object>>();
|
|
@@ -17,7 +19,7 @@ export function trackWallView({ identity, placement, exposure, currentIdentity,
|
|
|
17
19
|
active.set(key, copies);
|
|
18
20
|
let released = false;
|
|
19
21
|
let reported = false;
|
|
20
|
-
const answer = exposure.catch((
|
|
22
|
+
const answer = exposure.catch(logFailure("shared.wall-view-lifecycle", "Resolve wall experiment arm failed", null));
|
|
21
23
|
|
|
22
24
|
return {
|
|
23
25
|
exposure: answer,
|
|
@@ -17,14 +17,16 @@ export function boardYtdBase(payload: { asOf?: unknown; regularSessionDate?: unk
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* A period's header. YTD
|
|
21
|
-
*
|
|
20
|
+
* A period's header. YTD says what it is, a return against the close it is
|
|
21
|
+
* measured from, as long as `room` allows: `YTD vs 31 Dec close`, `YTD vs 31
|
|
22
|
+
* Dec`, then `YTD`.
|
|
22
23
|
*/
|
|
23
24
|
export function periodLabel(period: ThemePeriod, ytdBase: string | null, room = Infinity): string {
|
|
24
25
|
if (period !== "returnYtdPercent" || !ytdBase) return PERIOD_LABELS[period];
|
|
25
|
-
const labels = [`YTD vs ${ytdBase} close`, `YTD vs ${ytdBase}`,
|
|
26
|
+
const labels = [`YTD vs ${ytdBase} close`, `YTD vs ${ytdBase}`, "YTD"];
|
|
26
27
|
return labels.find((label) => label.length <= room) ?? labels.at(-1)!;
|
|
27
28
|
}
|
|
29
|
+
|
|
28
30
|
export interface ThemeSort { columnId: string; direction: SortDirection }
|
|
29
31
|
export const DEFAULT_SORT: ThemeSort = { columnId: "changePercent", direction: "desc" };
|
|
30
32
|
export function percent(value: number | null): string {
|
|
@@ -32,12 +34,17 @@ export function percent(value: number | null): string {
|
|
|
32
34
|
const rounded = Number(value.toFixed(2));
|
|
33
35
|
return `${rounded > 0 ? "+" : ""}${rounded.toFixed(2)}%`;
|
|
34
36
|
}
|
|
37
|
+
/** How many of a theme's names a figure is drawn from, when not all of them: `7 of 8`. Empty when every name is in it. */
|
|
38
|
+
export function coverageText(metric: ThemeAggregate): string {
|
|
39
|
+
return metric.covered < metric.total ? `${metric.covered} of ${metric.total}` : "";
|
|
40
|
+
}
|
|
35
41
|
export function aggregateText(metric: ThemeAggregate, breadth = false): string {
|
|
36
42
|
const value = breadth && metric.value !== null ? `${Math.round(metric.value)}%` : percent(metric.value);
|
|
37
|
-
|
|
43
|
+
const coverage = coverageText(metric);
|
|
44
|
+
return `${value}${coverage ? ` ${coverage}` : ""}`;
|
|
38
45
|
}
|
|
39
46
|
export const memberPrice = (value: number | null) => value === null ? "--" : value.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
40
|
-
export const coverageWidth = (metrics: readonly ThemeAggregate[]) => Math.max(0, ...metrics.map((metric) => metric
|
|
47
|
+
export const coverageWidth = (metrics: readonly ThemeAggregate[]) => Math.max(0, ...metrics.map((metric) => coverageText(metric) ? coverageText(metric).length + 1 : 0));
|
|
41
48
|
export function sortThemes(rows: readonly ThemeSummary[], sort: ThemeSort): ThemeSummary[] {
|
|
42
49
|
const value = (row: ThemeSummary) => {
|
|
43
50
|
if (sort.columnId === "name") return row.name;
|
|
@@ -152,9 +159,11 @@ export function themeColumns(width: number, rows: readonly ThemeSummary[], ytdBa
|
|
|
152
159
|
const metricWidth = (period: ThemePeriod) => Math.max(8, ...rows.map((row) => percent(row.returns[period].value).length)) + suffixWidth(period);
|
|
153
160
|
// The same trailing space is reserved in every cell and its header. A note
|
|
154
161
|
// occupies that space without moving the number away from its label.
|
|
155
|
-
const label = (period: ThemePeriod, room
|
|
162
|
+
const label = (period: ThemePeriod, room: number) => periodLabel(period, ytdBase, room) + " ".repeat(suffixWidth(period));
|
|
163
|
+
// The board always keeps room for `YTD vs 31 Dec`, so even a narrow one says what its YTD is measured against.
|
|
164
|
+
const leastRoom = ytdBase ? `YTD vs ${ytdBase}`.length : 0;
|
|
156
165
|
const columns = [column("name", "Theme", 26, "left"), column("memberCount", "N", 3),
|
|
157
|
-
...THEME_PERIODS.map((period) => column(period, label(period,
|
|
166
|
+
...THEME_PERIODS.map((period) => column(period, label(period, leastRoom), Math.max(metricWidth(period), label(period, leastRoom).length + HEADER_ROOM))),
|
|
158
167
|
column("breadth", "Breadth" + " ".repeat(coverageWidth(rows.map((row) => row.breadth))), 7 + coverageWidth(rows.map((row) => row.breadth))),
|
|
159
168
|
column("best", "Best 1D", 15), column("worst", "Worst 1D", 15)];
|
|
160
169
|
const fitted = fitColumns(columns, width, ["worst", "best", "return3MPercent", "returnYtdPercent", "return1MPercent", "return1WPercent", "memberCount"]);
|
|
@@ -5,14 +5,14 @@ import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
|
5
5
|
import type { ExtendedSession } from "../../../market-data/market/status";
|
|
6
6
|
import { useAsyncResource, useAutoRefresh, usePluginPaneState, usePluginTickerActions, useUpdatedAgo } from "../../../public/react";
|
|
7
7
|
import { usePaneInstance } from "../../../state/app/context";
|
|
8
|
-
import {
|
|
8
|
+
import { shownPriceColor } from "../../../theme/colors";
|
|
9
9
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
10
10
|
import { Box, Text } from "../../../ui";
|
|
11
11
|
import { nextHeaderSort } from "../../../utils/sort-values";
|
|
12
12
|
import { useMembersMenu } from "../shared/members-menu";
|
|
13
13
|
import { cachedMembers, cachedThemes, loadMembers, loadThemes } from "./client";
|
|
14
14
|
import {
|
|
15
|
-
aggregateText, boardYtdBase, coverageWidth, DEFAULT_SORT, extendedMove, matchTheme, memberColumns, memberExtendedSessions, memberPrice, percent,
|
|
15
|
+
aggregateText, boardYtdBase, coverageText, coverageWidth, DEFAULT_SORT, extendedMove, matchTheme, memberColumns, memberExtendedSessions, memberPrice, percent,
|
|
16
16
|
isLiveBoard, LIVE_BOARD_REFRESH_MS, SNAPSHOT_STALE_MS, snapshotFooterText, sortMembers, sortThemes, themeColumns, type ThemeSort,
|
|
17
17
|
} from "./model";
|
|
18
18
|
|
|
@@ -135,14 +135,14 @@ export function ThemesBoard({ width, height, focused, tabStrip, tabRows = 0 }: T
|
|
|
135
135
|
if (column.id === "best" || column.id === "worst") {
|
|
136
136
|
const member = row[column.id];
|
|
137
137
|
return { text: member ? `${member.symbol} ${percent(member.changePercent)}` : "--", value: member?.changePercent ?? null,
|
|
138
|
-
color: member ?
|
|
138
|
+
color: member ? shownPriceColor(member.changePercent, 2, colors) : colors.textMuted };
|
|
139
139
|
}
|
|
140
140
|
const breadth = column.id === "breadth";
|
|
141
141
|
const metric = breadth ? row.breadth : row.returns[column.id as ThemePeriod];
|
|
142
142
|
const suffixWidth = coverageWidth((data?.themes ?? []).map((theme) => breadth ? theme.breadth : theme.returns[column.id as ThemePeriod]));
|
|
143
|
-
const color = metric.value === null || breadth ? colors.textMuted :
|
|
143
|
+
const color = metric.value === null || breadth ? colors.textMuted : shownPriceColor(metric.value, 2, colors);
|
|
144
144
|
const valueText = breadth && metric.value !== null ? `${Math.round(metric.value)}%` : percent(metric.value);
|
|
145
|
-
const suffix = metric
|
|
145
|
+
const suffix = coverageText(metric);
|
|
146
146
|
return { text: aggregateText(metric, breadth), value: metric.value,
|
|
147
147
|
color, content: suffixWidth ? <Box flexDirection="row" width={column.width} justifyContent="flex-end">
|
|
148
148
|
<Text fg={color}>{valueText}</Text><Text fg={colors.textMuted}>{suffix.padStart(suffixWidth)}</Text>
|
|
@@ -16,12 +16,12 @@ import { TIME_RANGES, type TimeRange } from "../../../../time-series/range";
|
|
|
16
16
|
import type { PaneProps } from "../../../../types/plugin";
|
|
17
17
|
import type { PricePoint } from "../../../../types/financials";
|
|
18
18
|
import { colors, priceColor } from "../../../../theme/colors";
|
|
19
|
-
import { formatCompact, formatPercent } from "../../../../utils/format";
|
|
19
|
+
import { displayWidth, formatCompact, formatPercent } from "../../../../utils/format";
|
|
20
20
|
import { isPlainKeyboardEvent } from "../../../../utils/keyboard";
|
|
21
21
|
import { publicTickerKey } from "../../../../utils/exchanges";
|
|
22
|
-
import { quoteFormatOptions } from "../../../../market-data/market/format";
|
|
22
|
+
import { isPenceQuotedLine, penceFigure, quoteFormatOptions } from "../../../../market-data/market/format";
|
|
23
23
|
import {
|
|
24
|
-
barPriceDecimals, formatBarPrice, historyAllStart, historyBarsAfterHole, historyChangeBaseNote, historyMissingPeriodsNote,
|
|
24
|
+
barPriceDecimals, formatBarPrice, historyAllStart, historyBarsAfterHole, historyChangeBaseNote, historyMissingPeriodsNote, quotedPriceDivisor,
|
|
25
25
|
} from "../../../../cli/history-rows";
|
|
26
26
|
import {
|
|
27
27
|
MISSING_VOLUME_TEXT, missingVolumeCount, missingVolumeNotice, pricePointValues, priceHistoryIntegrityNotice,
|
|
@@ -89,8 +89,8 @@ function formatMaybePrice(value: number | null | undefined, decimals: number, wi
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/** The decimals the table's prices need: the one rule `history` and `fn HP` print by (barPriceDecimals). */
|
|
92
|
-
function historicalPriceDecimals(rows: readonly HistoricalPriceRow[], assetCategory?: string): number {
|
|
93
|
-
return barPriceDecimals(rows.map(({ point }) => point), assetCategory);
|
|
92
|
+
function historicalPriceDecimals(rows: readonly HistoricalPriceRow[], assetCategory?: string, priceDivisor = 1): number {
|
|
93
|
+
return barPriceDecimals(rows.map(({ point }) => point), assetCategory, priceDivisor);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/** The exported price: the digits the table shows, without the width limit. */
|
|
@@ -178,19 +178,27 @@ const HISTORY_NUMERIC_IDS = ["open", "high", "low", "close", "change", "changePe
|
|
|
178
178
|
/**
|
|
179
179
|
* Fixed date and volume columns; the price columns take the rest of the pane
|
|
180
180
|
* evenly, and give up cells down to a floor when it is narrow, so the volume
|
|
181
|
-
* stays on screen.
|
|
181
|
+
* stays on screen. A close with a note beside it (a London line's pence) asks
|
|
182
|
+
* for `closeWidth`, the widest close and note: the other columns give way to
|
|
183
|
+
* it first, down to their minimum, before it gives up its note.
|
|
182
184
|
*/
|
|
183
|
-
function buildHistoryColumns(width: number, intraday: boolean, size: HistoryBarSize, newestDate: string): HistoryColumn[] {
|
|
185
|
+
function buildHistoryColumns(width: number, intraday: boolean, size: HistoryBarSize, newestDate: string, closeWidth = 0): HistoryColumn[] {
|
|
184
186
|
// A month reads as `2026-10`; the newest bar, when its week or month runs on, adds `MTD` or `WTD`.
|
|
185
187
|
const dateWidth = Math.max(intraday ? 16 : size === "1mo" ? 7 : 10, newestDate.length);
|
|
186
|
-
const numeric: number[] = HISTORY_NUMERIC_IDS.map((id) => id === "changePercent" ? HISTORY_PERCENT_WIDTH
|
|
188
|
+
const numeric: number[] = HISTORY_NUMERIC_IDS.map((id) => id === "changePercent" ? HISTORY_PERCENT_WIDTH
|
|
189
|
+
: id === "close" ? Math.max(HISTORY_PRICE_WIDTH, closeWidth) : HISTORY_PRICE_WIDTH);
|
|
187
190
|
// One gap per column plus the table's inset on both sides.
|
|
188
191
|
const available = width - dateWidth - HISTORY_VOLUME_WIDTH - 8 - 2;
|
|
189
192
|
let spare = available - numeric.reduce((sum, value) => sum + value, 0);
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
// The close keeps the width its note asks for while another column can give way, then gives it up too.
|
|
194
|
+
const closeAt = HISTORY_NUMERIC_IDS.indexOf("close");
|
|
195
|
+
for (const closeFloor of [Math.max(HISTORY_NUMERIC_MIN_WIDTH, closeWidth), HISTORY_NUMERIC_MIN_WIDTH]) {
|
|
196
|
+
const floor = (index: number) => index === closeAt ? closeFloor : HISTORY_NUMERIC_MIN_WIDTH;
|
|
197
|
+
for (let index = 0; spare < 0 && numeric.some((value, at) => value > floor(at)); index = (index + 1) % numeric.length) {
|
|
198
|
+
if (numeric[index]! > floor(index)) {
|
|
199
|
+
numeric[index]! -= 1;
|
|
200
|
+
spare += 1;
|
|
201
|
+
}
|
|
194
202
|
}
|
|
195
203
|
}
|
|
196
204
|
if (spare > 0) {
|
|
@@ -256,13 +264,25 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
|
|
|
256
264
|
// Daily and longer bars are dates; only intraday bars carry a time.
|
|
257
265
|
const intraday = rows.length > 0 ? rows.some((row) => row.timed) : range === "1D" || range === "1W";
|
|
258
266
|
const newestRowDate = rows[0]?.date ?? "";
|
|
259
|
-
const columns = useMemo(() => buildHistoryColumns(width, intraday, size, newestRowDate), [intraday, newestRowDate, size, width]);
|
|
260
267
|
const assetCategory = quoteFormatOptions(
|
|
261
268
|
financials?.quote,
|
|
262
269
|
ticker?.metadata.assetCategory,
|
|
263
270
|
financials?.quoteMetadata?.instrumentType,
|
|
264
271
|
).assetCategory;
|
|
265
|
-
const
|
|
272
|
+
const priceDivisor = quotedPriceDivisor(quote);
|
|
273
|
+
const priceDecimals = useMemo(() => historicalPriceDecimals(rows, assetCategory, priceDivisor), [assetCategory, priceDivisor, rows]);
|
|
274
|
+
// A London line's close reads in pence too (6.385 638.5p): the close column asks for room for the widest.
|
|
275
|
+
const pence = isPenceQuotedLine(quote);
|
|
276
|
+
const closeWidth = useMemo(() => !pence ? 0 : rows.reduce((widest, row) => {
|
|
277
|
+
const close = row.point.close;
|
|
278
|
+
const pence = penceFigure(exportPrice(close, priceDecimals), quote, priceDecimals);
|
|
279
|
+
// A cell more than the text, so the close never runs into the low before it.
|
|
280
|
+
return pence ? Math.max(widest, displayWidth(` ${formatMaybePrice(close, priceDecimals, Infinity)} ${pence}`)) : widest;
|
|
281
|
+
}, 0), [pence, priceDecimals, quote, rows]);
|
|
282
|
+
const columns = useMemo(
|
|
283
|
+
() => buildHistoryColumns(width, intraday, size, newestRowDate, closeWidth),
|
|
284
|
+
[closeWidth, intraday, newestRowDate, size, width],
|
|
285
|
+
);
|
|
266
286
|
const boundedSelectedIdx = rows.length > 0 ? Math.min(selectedIdx, rows.length - 1) : -1;
|
|
267
287
|
const cycleRange = useCallback(() => setRange((current) => nextHistoryRange(current)), [setRange]);
|
|
268
288
|
|
|
@@ -296,12 +316,17 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
|
|
|
296
316
|
case "open":
|
|
297
317
|
case "high":
|
|
298
318
|
case "low":
|
|
299
|
-
case "close":
|
|
319
|
+
case "close": {
|
|
320
|
+
const value = row.point[column.id];
|
|
321
|
+
// A London line's close also reads in the pence it trades in, where the column has room: 6.385 638.5p.
|
|
322
|
+
const pence = column.id === "close" ? penceFigure(exportPrice(value, priceDecimals), quote, priceDecimals) : null;
|
|
300
323
|
return {
|
|
301
|
-
text: formatMaybePrice(
|
|
302
|
-
value: exportPrice(
|
|
324
|
+
text: formatMaybePrice(value, priceDecimals, column.width),
|
|
325
|
+
value: exportPrice(value, priceDecimals),
|
|
326
|
+
...(pence ? { note: pence } : {}),
|
|
303
327
|
...(column.id === "close" ? { color: colors.textBright, attributes: TextAttributes.BOLD } : { color: colors.text }),
|
|
304
328
|
};
|
|
329
|
+
}
|
|
305
330
|
case "change":
|
|
306
331
|
return { text: spanMark(formatMaybePrice(row.change, priceDecimals, column.width - (row.changeSpansGap ? 1 : 0)), row, row.change),
|
|
307
332
|
value: exportPrice(row.change, priceDecimals), color: priceColor(row.change ?? 0) };
|
|
@@ -311,7 +336,7 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
|
|
|
311
336
|
case "volume":
|
|
312
337
|
return { text: formatVolume(row.point.volume, volumeMissing > 0), value: row.point.volume ?? null, color: colors.textDim };
|
|
313
338
|
}
|
|
314
|
-
}, [priceDecimals, volumeMissing]);
|
|
339
|
+
}, [priceDecimals, quote, volumeMissing]);
|
|
315
340
|
|
|
316
341
|
// ALL starts where the source's bars do, when that is well inside the window (BTC-USD's in 2014-09).
|
|
317
342
|
const oldestDate = rows.at(-1)?.exportDate;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../../utils/log-failure";
|
|
1
2
|
import { fetchHistoryResult, InvalidHistoryResultError } from "../../../../sources/history-result";
|
|
2
3
|
import { historyRetentionNotice, isHistoryRetentionError } from "../../../../sources/history-retention";
|
|
3
4
|
import {
|
|
@@ -74,8 +75,8 @@ async function sessionVenue(provider: DataProvider, symbol: string, exchange: st
|
|
|
74
75
|
? await provider.getQuoteMetadata(symbol, exchange)
|
|
75
76
|
: await provider.getQuote(symbol, exchange);
|
|
76
77
|
return metadata?.listingExchangeName ?? "";
|
|
77
|
-
} catch {
|
|
78
|
-
return "";
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return logFailure("ticker-detail.data-panes.returns-history", "Read return session venue failed", "")(error);
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
|
|
@@ -17,7 +17,9 @@ import {
|
|
|
17
17
|
import { isRoundTheClockCoin } from "../../../../utils/crypto-pair";
|
|
18
18
|
import { currentFxClosureStart, isCurrencyPairListing } from "../../../../utils/fx-market-hours";
|
|
19
19
|
import { pricePointValues } from "../../../../utils/price-history-integrity";
|
|
20
|
+
import { timeZoneCity, zonedClock } from "../../../../utils/utc-time";
|
|
20
21
|
import { zonedDateKey } from "../../../../utils/zoned-date-time";
|
|
22
|
+
import { historyDateHeader } from "./history-bars";
|
|
21
23
|
|
|
22
24
|
export type ReturnRange = "6H" | "1D" | "5D" | TimeRange;
|
|
23
25
|
|
|
@@ -347,6 +349,66 @@ export function returnBaseCloseDay(
|
|
|
347
349
|
return period ? barLastSession(day, period, venue, now) ?? barCloseDay(day, resolution, venue) : day;
|
|
348
350
|
}
|
|
349
351
|
|
|
352
|
+
/**
|
|
353
|
+
* The clock an intraday bar's time is shown on: its venue's own, where the app
|
|
354
|
+
* knows its sessions, so the open reads 09:30 and not 13:30; null for UTC, which
|
|
355
|
+
* is where a coin, a currency pair or a venue with no known zone keeps its bars.
|
|
356
|
+
*/
|
|
357
|
+
function returnClockZone(venue: string | undefined): string | null {
|
|
358
|
+
if (!venue || isAlwaysOpenExchange(venue)) return null;
|
|
359
|
+
return sessionCalendarTimeZone(venue);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* A bar as the table dates it: an intraday bar's open on its venue's clock
|
|
364
|
+
* (`2026-10-23 09:30`), a daily or longer bar its session day.
|
|
365
|
+
*/
|
|
366
|
+
export function returnTimeText(timestamp: number, resolution: ManualChartResolution, venue: string | undefined): string {
|
|
367
|
+
if (!isIntradayResolution(resolution)) return barSessionDay(timestamp, venue);
|
|
368
|
+
const zone = returnClockZone(venue);
|
|
369
|
+
if (!zone) return new Date(timestamp).toISOString().slice(0, 16).replace("T", " ");
|
|
370
|
+
const clock = zonedClock(timestamp, zone);
|
|
371
|
+
return `${clock.date} ${clock.time}`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* What the time column is headed: the clock its times are on (`ET` for New York,
|
|
376
|
+
* as TAS heads it, the venue's city for another zone, else `UTC`), or what a
|
|
377
|
+
* daily or longer bar is dated by.
|
|
378
|
+
*/
|
|
379
|
+
export function returnTimeHeader(resolution: ManualChartResolution, venue: string | undefined): string {
|
|
380
|
+
if (!isIntradayResolution(resolution)) return historyDateHeader(resolution)?.toUpperCase() ?? "DATE";
|
|
381
|
+
const zone = returnClockZone(venue);
|
|
382
|
+
return `TIME ${!zone ? "UTC" : zone === "America/New_York" ? "ET" : timeZoneCity(zone).toUpperCase()}`;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** The figures a window's rows add up to: its total, and the bars that moved most each way. */
|
|
386
|
+
export interface ReturnSummary {
|
|
387
|
+
/** The newest row's total return, a fraction. Null when no bar has one. */
|
|
388
|
+
total: number | null;
|
|
389
|
+
best: ReturnRow | null;
|
|
390
|
+
worst: ReturnRow | null;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* The window's total return (the newest priced row's, measured from the base
|
|
395
|
+
* close) with its best and worst interval. A single bar has no interval worth
|
|
396
|
+
* ranking, and a window whose every interval is the same move has no best or
|
|
397
|
+
* worst, so neither is named then.
|
|
398
|
+
*/
|
|
399
|
+
export function summarizeReturns(rows: readonly ReturnRow[]): ReturnSummary {
|
|
400
|
+
// Rows are newest first.
|
|
401
|
+
const total = rows.find((row) => row.cumulativePercent !== null)?.cumulativePercent ?? null;
|
|
402
|
+
const moves = rows.filter((row) => row.intervalPercent !== null);
|
|
403
|
+
let best: ReturnRow | null = null;
|
|
404
|
+
let worst: ReturnRow | null = null;
|
|
405
|
+
for (const row of moves) {
|
|
406
|
+
if (!best || row.intervalPercent! > best.intervalPercent!) best = row;
|
|
407
|
+
if (!worst || row.intervalPercent! < worst.intervalPercent!) worst = row;
|
|
408
|
+
}
|
|
409
|
+
return best && worst && best.intervalPercent !== worst.intervalPercent ? { total, best, worst } : { total, best: null, worst: null };
|
|
410
|
+
}
|
|
411
|
+
|
|
350
412
|
/** Whether the newest daily row is the session still trading at its venue, which its table marks. */
|
|
351
413
|
export function isTodayReturnRow(row: Pick<ReturnRow, "timestamp"> | undefined, resolution: ManualChartResolution, venue: string | undefined, now: number): boolean {
|
|
352
414
|
return !!row && resolution === "1d" && isIntradayDailyBar(barSessionDay(row.timestamp, venue), venue, now);
|