@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
|
@@ -2,7 +2,7 @@ import type { PaneTemplateInstanceConfig } from "../../../types/plugin";
|
|
|
2
2
|
import type { PluginModule } from "../plugin-module";
|
|
3
3
|
import { commandRequestParams } from "../shared/command-tab";
|
|
4
4
|
import { PERP_SERIES } from "./series";
|
|
5
|
-
import { perpsCache, perpsHistoryCache, perpsListCache, perpsMarketCache, perpsRankingsCache } from "./client";
|
|
5
|
+
import { perpsCache, perpsDepthCache, perpsHistoryCache, perpsLiquidationsCache, perpsListCache, perpsMarketCache, perpsRankingsCache } from "./client";
|
|
6
6
|
import { perpsHeadless } from "./headless";
|
|
7
7
|
import { PerpsPane } from "./pane";
|
|
8
8
|
|
|
@@ -18,16 +18,18 @@ export function perpsInstance(arg: string | undefined): PaneTemplateInstanceConf
|
|
|
18
18
|
settings: { market }, params: commandRequestParams({ market }) };
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
const PERP_CACHES = [perpsCache, perpsHistoryCache, perpsListCache, perpsMarketCache, perpsRankingsCache, perpsLiquidationsCache, perpsDepthCache] as const;
|
|
22
|
+
|
|
21
23
|
export const perpsModule: PluginModule = {
|
|
22
24
|
series: PERP_SERIES,
|
|
23
25
|
panes: [{ id: "perps", name: "Perpetuals", icon: "P", component: PerpsPane, defaultPosition: "right", defaultMode: "floating",
|
|
24
26
|
defaultFloatingSize: { width: 138, height: 34 }, tableExport: true, headless: perpsHeadless }],
|
|
25
|
-
paneTemplates: [{ id: "perps-pane", paneId: "perps", label: "Perpetuals", description: "Perpetual funding, open interest and premiums by market and venue, with rankings, a venue comparison and Pro history.",
|
|
26
|
-
keywords: ["perp", "perpetual", "perpetuals", "funding", "funding rate", "open interest", "hip-3", "stock perps", "basis", "basis trade", "carry", "venue", "funding spread", "long short ratio", "long/short", "positioning"],
|
|
27
|
+
paneTemplates: [{ id: "perps-pane", paneId: "perps", label: "Perpetuals", description: "Perpetual funding, open interest and premiums by market and venue, with rankings, a venue comparison, liquidations, order book depth and Pro history.",
|
|
28
|
+
keywords: ["perp", "perpetual", "perpetuals", "funding", "funding rate", "open interest", "hip-3", "stock perps", "basis", "basis trade", "carry", "venue", "funding spread", "long short ratio", "long/short", "positioning", "liquidations", "liquidation", "order book depth", "market depth", "liquidity"],
|
|
27
29
|
shortcut: { prefix: "PERP", argKind: "text", argOptional: true, argPlaceholder: "market" }, headless: perpsHeadless,
|
|
28
30
|
createInstance: (_ctx, options) => perpsInstance(options?.arg) }],
|
|
29
31
|
setup(ctx) {
|
|
30
|
-
for (const cache of
|
|
32
|
+
for (const cache of PERP_CACHES) cache.attach(ctx.persistence);
|
|
31
33
|
},
|
|
32
|
-
dispose() { for (const cache of
|
|
34
|
+
dispose() { for (const cache of PERP_CACHES) cache.reset(); },
|
|
33
35
|
};
|
|
@@ -6,14 +6,28 @@ import { formatCompact } from "../../../utils/format";
|
|
|
6
6
|
import { isUsListingExchange, publicTickerKey } from "../../../utils/exchanges";
|
|
7
7
|
import { isRecord } from "../../../utils/guards";
|
|
8
8
|
import type { ThemeColors } from "../../../theme/colors";
|
|
9
|
-
export const PERP_TABS = ["board", "rankings", "compare", "history", "evidence"] as const;
|
|
9
|
+
export const PERP_TABS = ["board", "rankings", "compare", "liquidations", "depth", "history", "evidence"] as const;
|
|
10
10
|
export type PerpTab = typeof PERP_TABS[number];
|
|
11
11
|
/** One market's tabs; the others list many markets. */
|
|
12
12
|
export const isMarketTab = (tab: string) => tab === "history" || tab === "evidence";
|
|
13
|
-
/**
|
|
14
|
-
export const
|
|
13
|
+
/** One base asset's tabs, across its contracts on every venue; they share the asset. */
|
|
14
|
+
export const isAssetTab = (tab: string) => tab === "compare" || tab === "liquidations" || tab === "depth";
|
|
15
|
+
/**
|
|
16
|
+
* A named market opens its history, or its latest values (Evidence) where history is Pro's, so a free account
|
|
17
|
+
* lands on the figures it has and not on a lock; nothing named opens the board.
|
|
18
|
+
*/
|
|
19
|
+
export const openingTab = (market: string | null | undefined, pro = true): PerpTab => market?.trim() ? pro ? "history" : "evidence" : "board";
|
|
15
20
|
export const ASSET_FILTERS = ["all", "stocks", "indices", "energy", "metals", "fx", "crypto"] as const;
|
|
16
|
-
|
|
21
|
+
/** The venues Gloom collects perpetuals from: what the venue filter and `--venue` take. */
|
|
22
|
+
export const VENUES = ["hyperliquid", "binance", "bybit", "okx"] as const;
|
|
23
|
+
/** Every venue a market identity can name (`kraken:PF_XBTUSD`), collected or not. */
|
|
24
|
+
export const IDENTITY_VENUES = [...VENUES, "deribit", "coinbase", "kraken", "dydx"] as const;
|
|
25
|
+
/** What `--venue` says of a venue Gloom does not collect yet; null for a covered venue and for any other word. */
|
|
26
|
+
export function uncoveredVenueNote(venue: string): string | null {
|
|
27
|
+
const id = venue.trim().toLowerCase();
|
|
28
|
+
return (IDENTITY_VENUES as readonly string[]).includes(id) && !(VENUES as readonly string[]).includes(id)
|
|
29
|
+
? `Gloom doesn't cover ${venueName(id)} perps yet.` : null;
|
|
30
|
+
}
|
|
17
31
|
export const BOARD_SORTS = ["oi", "funding", "oi-change", "premium", "long-short"] as const;
|
|
18
32
|
export const sortLabel = (sort: string) => ({ oi: "OI", funding: "Funding", "oi-change": "OI change", premium: "Premium", "long-short": "Long %" })[sort] ?? sort;
|
|
19
33
|
export const label = (s: string) => s === "fx" ? "FX" : s.charAt(0).toUpperCase() + s.slice(1);
|
|
@@ -5,13 +5,10 @@ import type { PerpBoardRow, PerpMarketPayload } from "../../../api-client/perps"
|
|
|
5
5
|
import { buildSectionedRows, ChartTableHeader, DataTableView, EmptyState, KeyValueRow, SectionHeading, PaneStatusBody, QueryBar, StatGrid, useChartTableSelection,
|
|
6
6
|
usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, useQueryBarSearch, type DataTableCell, type KeyValueRowProps, type PaneFooterSegment, type PaneHint, type SectionedRow,
|
|
7
7
|
type StatItem } from "../../../components";
|
|
8
|
-
import { deriveHeadlessFreshness, paneStatusLineVariants } from "../../../cli/pane-functions/freshness";
|
|
9
8
|
import { openFormModal } from "../../../components/form-modal";
|
|
10
9
|
import { tf } from "../../../i18n";
|
|
11
10
|
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
12
11
|
import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePaneTitle, usePluginAppActions, usePluginPaneState } from "../../../public/react";
|
|
13
|
-
import { useAgeClock } from "../../../react/auto-refresh";
|
|
14
|
-
import { useRemoteUiNode } from "../../../remote/semantic-tree";
|
|
15
12
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
16
13
|
import type { PaneProps } from "../../../types/plugin";
|
|
17
14
|
import { Box, ScrollBox, useRendererHost, useUiCapabilities } from "../../../ui";
|
|
@@ -25,9 +22,11 @@ import { isCloudSessionRequired, useResearchCloudSession } from "../shared/resea
|
|
|
25
22
|
import { boardSearchText, cachedPerpSelection, cachedPerpsBoard, cachedPerpsCompare, cachedPerpsRankings, loadPerpSelection, loadPerpsBoard, loadPerpsCompare,
|
|
26
23
|
loadPerpsHistoryForRange, loadPerpsRankings } from "./client";
|
|
27
24
|
import { perpFreshness } from "./freshness";
|
|
25
|
+
import { Depth, Liquidations } from "./cascade-tabs";
|
|
26
|
+
import { useFreshnessStatusLine } from "./status-line";
|
|
28
27
|
import { useLivePerpRows, useLivePerpTableRows } from "./live-rows";
|
|
29
28
|
import { aprFormula, ASSET_FILTERS, BOARD_SORTS, boardColumns, collectedVenueCount, compact, compareColumns, dayChange, fundingInterval, fundingSpread, historyCaption, historyCell, historyChange, historyColumns,
|
|
30
|
-
HISTORY_METRICS, historyRows, historySeries, historyValue, inPoints, isMarketTab, label, longShortReason, marketLabel, metricLabel, openingTab, perpCell, perpHistoryProText, PERP_TABS, percent,
|
|
29
|
+
HISTORY_METRICS, historyRows, historySeries, historyValue, inPoints, isAssetTab, isMarketTab, label, longShortReason, marketLabel, metricLabel, openingTab, perpCell, perpHistoryProText, PERP_TABS, percent,
|
|
31
30
|
positioningFields, price, rankingColumns, rankingRows, RANKINGS, ratioText, share, sortLabel, time, underlyingTicker, venueLabel, venueName, venuesOf, type HistoryMetric,
|
|
32
31
|
type PerpColumnId, type PerpTab, type RankingKey } from "./model";
|
|
33
32
|
|
|
@@ -45,17 +44,8 @@ const withRankedRow = (entry: SectionedRow<Ranked>, row: PerpBoardRow): Sectione
|
|
|
45
44
|
* same way.
|
|
46
45
|
*/
|
|
47
46
|
function usePerpStatusLine(rows: readonly PerpBoardRow[], funding: readonly PerpBoardRow[], since: number | null, live = false): PaneFooterSegment[] {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
useRemoteUiNode(declared ? { role: "report-freshness", label: "Venue freshness", getMetadata: () => ({ ...declared }) } : null);
|
|
51
|
-
return useMemo(() => {
|
|
52
|
-
if (!declared) return [];
|
|
53
|
-
const freshness = deriveHeadlessFreshness({ shape: "rows" }, { rows: [], freshness: declared }, now);
|
|
54
|
-
const tone = freshness.status === "stale" ? "warning" as const : "muted" as const;
|
|
55
|
-
// A footer too narrow beside its keys gives up the funding part before any venue's time.
|
|
56
|
-
const [line, ...shorter] = paneStatusLineVariants(freshness).map((text) => [{ text, tone }]);
|
|
57
|
-
return [{ id: "perp-freshness", asOf: true, parts: line!, variants: shorter }];
|
|
58
|
-
}, [declared, rows, now]);
|
|
47
|
+
const declare = useCallback((now: number) => rows.length ? perpFreshness(rows, { funding, now, live }) : null, [rows, funding, live]);
|
|
48
|
+
return useFreshnessStatusLine(declare, since);
|
|
59
49
|
}
|
|
60
50
|
|
|
61
51
|
type EvidenceField = [label: string, value: string, detail?: string, link?: KeyValueRowProps["link"]];
|
|
@@ -378,11 +368,12 @@ export function PerpsPane({ width, height, focused, marketAction }: Pick<PanePro
|
|
|
378
368
|
const openDescription = useOpenDescription();
|
|
379
369
|
const upgrade = useCloudUpgradeAction("perp");
|
|
380
370
|
const [openingMarket] = usePaneSettingValue("market", "");
|
|
381
|
-
const
|
|
371
|
+
const opening = openingTab(openingMarket, access.hasProAccess);
|
|
372
|
+
const [openingTabValue] = usePaneSettingValue("tab", opening);
|
|
382
373
|
// A market asked for again (a stock's perpetual row, PERP) comes back to it on its opening tab, whatever this pane
|
|
383
374
|
// shows by then.
|
|
384
375
|
const [savedTab, setTab] = useCommandValue<string>("perps", "perps:tab", (params) => params.market ? openingTabValue : null, openingTabValue);
|
|
385
|
-
const tab = PERP_TABS.includes(savedTab as PerpTab) ? savedTab as PerpTab :
|
|
376
|
+
const tab = PERP_TABS.includes(savedTab as PerpTab) ? savedTab as PerpTab : opening;
|
|
386
377
|
const marketTab = isMarketTab(tab);
|
|
387
378
|
const [search, setSearch] = useCommandValue("perps", "perps:search", (params) => params.market || null, openingMarket);
|
|
388
379
|
const [compareAsset, setCompareAsset] = usePluginPaneState<string>("perps:compare", compareOpening(openingMarket));
|
|
@@ -409,13 +400,17 @@ export function PerpsPane({ width, height, focused, marketAction }: Pick<PanePro
|
|
|
409
400
|
// The market on screen goes with the user: History and Evidence open the row picked on a list, Compare its base asset.
|
|
410
401
|
const current = marketTab ? active : listRow;
|
|
411
402
|
const carried = marketTab ? active : listRow ?? picked;
|
|
403
|
+
// Liquidations and Depth list no board rows: a market tab after them opens their asset, and they keep Compare's.
|
|
404
|
+
const assetOnly = tab === "liquidations" || tab === "depth";
|
|
412
405
|
const selectTab = useCallback((next: string) => {
|
|
413
|
-
if (
|
|
414
|
-
if (carried && next
|
|
406
|
+
if (assetOnly && isMarketTab(next)) setSearch(compareAsset);
|
|
407
|
+
else if (carried && isMarketTab(next) && !marketTab) setSearch(carried.marketId);
|
|
408
|
+
if (carried && isAssetTab(next) && !assetOnly) setCompareAsset(carried.baseAsset);
|
|
415
409
|
setListRow(null);
|
|
416
410
|
setTab(next);
|
|
417
|
-
}, [carried, marketTab, setSearch, setCompareAsset, setTab]);
|
|
418
|
-
const openMarket = useCallback((row: PerpBoardRow, next: PerpTab =
|
|
411
|
+
}, [assetOnly, carried, compareAsset, marketTab, setSearch, setCompareAsset, setTab]);
|
|
412
|
+
const openMarket = useCallback((row: PerpBoardRow, next: PerpTab = openingTab(row.marketId, access.hasProAccess)) => { setSearch(row.marketId); setTab(next); }, [setSearch, setTab, access.hasProAccess]);
|
|
413
|
+
const openMarketId = useCallback((marketId: string) => { setSearch(marketId); setTab(openingTab(marketId, access.hasProAccess)); }, [setSearch, setTab, access.hasProAccess]);
|
|
419
414
|
const { strip, rows: tabRows } = usePaneTabs({ tabs: PERP_TABS.map((value) => ({ value, label: label(value) })), activeValue: tab, onSelect: selectTab, focused, dense: true });
|
|
420
415
|
const refreshMarket = useCallback(() => { void resource.reload(); }, [resource.reload]);
|
|
421
416
|
useAutoRefresh(resource.updatedAt, resource.load, { intervalMs: 60_000 });
|
|
@@ -450,7 +445,9 @@ export function PerpsPane({ width, height, focused, marketAction }: Pick<PanePro
|
|
|
450
445
|
return <Box width={width} height={height} flexDirection="column">
|
|
451
446
|
{strip}
|
|
452
447
|
{tab === "board" ? <Board {...list} /> : tab === "rankings" ? <Rankings {...list} />
|
|
453
|
-
: tab === "compare" ? <Compare {...list} asset={compareAsset} onAssetChange={setCompareAsset} />
|
|
448
|
+
: tab === "compare" ? <Compare {...list} asset={compareAsset} onAssetChange={setCompareAsset} />
|
|
449
|
+
: tab === "liquidations" ? <Liquidations accessKey={accessKey} width={width} height={listHeight} focused={focused} asset={compareAsset} onAssetChange={setCompareAsset} onOpenMarket={openMarketId} />
|
|
450
|
+
: tab === "depth" ? <Depth accessKey={accessKey} width={width} height={listHeight} focused={focused} asset={compareAsset} onAssetChange={setCompareAsset} onOpenMarket={openMarketId} /> : <>
|
|
454
451
|
<QueryBar width={width} search={{ focused, value: search, onChange: setSearch, placeholder: "Market or canonical identity", debounceMs: 300, ...querySearch.searchProps }}
|
|
455
452
|
filters={tab === "history" ? [
|
|
456
453
|
{ id: "metric", label: "Series", value: metric, options: HISTORY_METRICS.map((value) => ({ value, label: metricLabel(value) })), onChange: setMetric },
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { deriveHeadlessFreshness, paneStatusLineVariants } from "../../../cli/pane-functions/freshness";
|
|
3
|
+
import type { PaneFooterSegment } from "../../../components";
|
|
4
|
+
import { useAgeClock } from "../../../react/auto-refresh";
|
|
5
|
+
import { useRemoteUiNode } from "../../../remote/semantic-tree";
|
|
6
|
+
import type { HeadlessPaneFreshness } from "../../../types/headless";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The footer line a tab ends with, from the same freshness its text report declares (`declare`, null while there is
|
|
10
|
+
* nothing to date): the source, its time and how old it is, the line `fn PERP` and `shot PERP` end with. It keeps ageing
|
|
11
|
+
* while the pane is seen (`since` is when the data loaded), and is published for a capture to date itself the same way.
|
|
12
|
+
*/
|
|
13
|
+
export function useFreshnessStatusLine(declare: ((now: number) => HeadlessPaneFreshness | null) | null, since: number | null): PaneFooterSegment[] {
|
|
14
|
+
const now = useAgeClock(since);
|
|
15
|
+
const declared = useMemo(() => declare?.(now) ?? null, [declare, now]);
|
|
16
|
+
useRemoteUiNode(declared ? { role: "report-freshness", label: "Venue freshness", getMetadata: () => ({ ...declared }) } : null);
|
|
17
|
+
return useMemo(() => {
|
|
18
|
+
if (!declared) return [];
|
|
19
|
+
const freshness = deriveHeadlessFreshness({ shape: "rows" }, { rows: [], freshness: declared }, now);
|
|
20
|
+
const tone = freshness.status === "stale" ? "warning" as const : "muted" as const;
|
|
21
|
+
// A footer too narrow beside its keys gives up the note before any venue's time.
|
|
22
|
+
const [line, ...shorter] = paneStatusLineVariants(freshness).map((text) => [{ text, tone }]);
|
|
23
|
+
return [{ id: "perp-freshness", asOf: true, parts: line!, variants: shorter }];
|
|
24
|
+
}, [declared, now]);
|
|
25
|
+
}
|
|
@@ -161,7 +161,11 @@ export function composeBuiltinPlugin(options: CompositePluginOptions): GloomPlug
|
|
|
161
161
|
for (const module of modules) {
|
|
162
162
|
startedModules.push(module);
|
|
163
163
|
try {
|
|
164
|
-
|
|
164
|
+
// Only an async setup is waited for. Awaiting a synchronous one hands
|
|
165
|
+
// the tick to the panes first: Gloom Cloud restores the saved session
|
|
166
|
+
// in its second module, and the panes' first requests went out without it.
|
|
167
|
+
const pending = module.setup?.(ctx);
|
|
168
|
+
if (pending) await pending;
|
|
165
169
|
} catch (error) {
|
|
166
170
|
// Logged as the plugin, so the failure counts toward its health.
|
|
167
171
|
ctx.log.error("Module setup failed", { error: error instanceof Error ? error.message : String(error) });
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
1
2
|
import { peekPersistedBrokerAccounts } from "../../../brokers/account-cache";
|
|
2
3
|
import type { AppResourceStorePort } from "../../../core/app-service-ports";
|
|
3
4
|
import type { AppConfig } from "../../../types/config";
|
|
@@ -20,8 +21,9 @@ export function peekCachedBrokerAccounts(
|
|
|
20
21
|
try {
|
|
21
22
|
const accounts = peekPersistedBrokerAccounts(resources, instance);
|
|
22
23
|
if (accounts?.length) brokerAccounts[instance.id] = accounts;
|
|
23
|
-
} catch {
|
|
24
|
+
} catch (error) {
|
|
24
25
|
// An unreadable snapshot leaves the portfolio without account figures.
|
|
26
|
+
logFailure("portfolio-list.cached-account", "Read cached broker accounts failed")(error);
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
return brokerAccounts;
|
|
@@ -8,6 +8,7 @@ import { splitSymbolArgs, splitSymbolWords, symbolInputKey } from "../../../../c
|
|
|
8
8
|
import { notASymbolError } from "../../../../cli/symbol-cells";
|
|
9
9
|
import { readSymbolList, SymbolListError } from "../../../../cli/symbol-list";
|
|
10
10
|
import { cliStyles } from "../../../../utils/cli-output";
|
|
11
|
+
import { shellWord } from "../../../../utils/shell-word";
|
|
11
12
|
import { dryRunNote } from "../../../../cli/helpers";
|
|
12
13
|
import {
|
|
13
14
|
addSymbol,
|
|
@@ -46,14 +47,7 @@ export const SYMBOL_LIST_HELP = {
|
|
|
46
47
|
],
|
|
47
48
|
};
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
* One word of a command line as a shell reads it back: as written when it
|
|
51
|
-
* holds nothing a shell treats specially (`NVDA`, `SAN:EPA`, `BRK.B`), else
|
|
52
|
-
* in single quotes (`'Altria Group'`, `'$nvda'`, `'AT&T'`).
|
|
53
|
-
*/
|
|
54
|
-
export function shellWord(word: string): string {
|
|
55
|
-
return /^[\p{L}\p{N}._:@%+,/][\p{L}\p{N}._:@%+,/=-]*$/u.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
56
|
-
}
|
|
50
|
+
export { shellWord };
|
|
57
51
|
|
|
58
52
|
/**
|
|
59
53
|
* A row's line, and under it the listing a company's name was read as, the
|
|
@@ -50,7 +50,7 @@ import { importSource, notionalPositions, rankThirteenFLines, type ThirteenFImpo
|
|
|
50
50
|
import { importedFromText } from "../imported-from";
|
|
51
51
|
import { savedListingCoinNote } from "../../../../cli/commands/crypto-hints";
|
|
52
52
|
import { findNamedCollection } from "../../../../portfolio/collection-name";
|
|
53
|
-
import {
|
|
53
|
+
import { ambiguousQuantityProblem } from "../../../../portfolio/position-quantity";
|
|
54
54
|
import {
|
|
55
55
|
CLI_COST_FX_WORDING, loadPurchaseDateCostFx, readCostFxRate, readPurchaseDate, resolvePositionCostFx, type CostFxHistoryClient,
|
|
56
56
|
} from "../../../../portfolio/cost-fx";
|
|
@@ -363,8 +363,8 @@ async function setPositionCommand(
|
|
|
363
363
|
const avgCost = parseAmountArg(avgCostValue, "Average cost");
|
|
364
364
|
// The notes say which listing a bare symbol went to, and that a bare coin ticker is not the coin, as `portfolio add` does.
|
|
365
365
|
const { ticker, notes } = await resolveCliTicker(symbol, store, dataProvider, exchange);
|
|
366
|
-
// `1.250`
|
|
367
|
-
const lots =
|
|
366
|
+
// `1.250` and `1.000` ask which was meant, as `1,250` did above: a dot before three digits is as often thousands.
|
|
367
|
+
const lots = ambiguousQuantityProblem(sharesValue, ticker, { listing: savedListingName(ticker).key });
|
|
368
368
|
if (lots) throw new PortfolioCliError(lots);
|
|
369
369
|
// A future is held in contracts of the size its terms give, as `quote` states it, and a cost typed as
|
|
370
370
|
// corn is quoted counts cents.
|
|
@@ -942,7 +942,7 @@ const AMOUNTS_HELP = {
|
|
|
942
942
|
lines: [
|
|
943
943
|
"Numbers take either decimal mark: 1.234,5, 1234,5 and 1,234.5 are 1234.5, and 18,50 is 18.5.",
|
|
944
944
|
"Cash and --notional are amounts of a currency (cash in the portfolio's unless named, --notional in USD), so 500,000 and 1.234 are thousands, a third decimal being below a cent. In a currency with three decimals (KWD, BHD, OMR) they could be either, and the command asks which.",
|
|
945
|
-
"
|
|
945
|
+
"Average cost reads a lone dot as a decimal point (123.456). Shares and contracts ask which a lone dot or comma before three digits meant (1.000, 1,234), thousands or a decimal, and name both readings to type.",
|
|
946
946
|
],
|
|
947
947
|
};
|
|
948
948
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../../utils/log-failure";
|
|
1
2
|
import { createBaseConverter } from "../../../../cli/base-converter";
|
|
2
3
|
import { savedListingName } from "../../../../cli/listing-arg";
|
|
3
4
|
import { currentListingName } from "../../../../tickers/listing-name";
|
|
@@ -257,8 +258,9 @@ export async function loadCollectionQuotes(
|
|
|
257
258
|
const quote = await dataProvider.getQuote(ticker.metadata.ticker, ticker.metadata.exchange,
|
|
258
259
|
datesChangeBasis ? { ...context, datesChangeBasis } : context);
|
|
259
260
|
quotes.set(ticker.metadata.ticker, quote);
|
|
260
|
-
} catch {
|
|
261
|
-
//
|
|
261
|
+
} catch (error) {
|
|
262
|
+
// A partial quote failure leaves the rest of the table rendering.
|
|
263
|
+
logFailure("portfolio-list.cli.render", "Load collection quote failed")(error);
|
|
262
264
|
}
|
|
263
265
|
}),
|
|
264
266
|
);
|
|
@@ -1269,7 +1271,7 @@ async function loadMarketCapFinancials(
|
|
|
1269
1271
|
const targets = needed.map((ticker) => ({ symbol: ticker.metadata.ticker, exchange: ticker.metadata.exchange }));
|
|
1270
1272
|
if (dataProvider.getTickerFinancialsBatch) {
|
|
1271
1273
|
await Promise.all(chunked(targets, FINANCIALS_BATCH_SIZE).map(async (batch) => {
|
|
1272
|
-
const results = await dataProvider.getTickerFinancialsBatch!(batch).catch((
|
|
1274
|
+
const results = await dataProvider.getTickerFinancialsBatch!(batch).catch(logFailure("portfolio-list.cli.render", "Load market cap financials batch failed", []));
|
|
1273
1275
|
batch.forEach((target, index) => {
|
|
1274
1276
|
const value = (results.find((result) => result.target === target) ?? results[index])?.financials;
|
|
1275
1277
|
if (value) financials.set(target.symbol, value);
|
|
@@ -1278,7 +1280,7 @@ async function loadMarketCapFinancials(
|
|
|
1278
1280
|
return financials;
|
|
1279
1281
|
}
|
|
1280
1282
|
await mapWithConcurrency(targets, FINANCIALS_CONCURRENCY, async (target) => {
|
|
1281
|
-
const value = await dataProvider.getTickerFinancials(target.symbol, target.exchange).catch((
|
|
1283
|
+
const value = await dataProvider.getTickerFinancials(target.symbol, target.exchange).catch(logFailure("portfolio-list.cli.render", "Load market cap financials failed", null));
|
|
1282
1284
|
if (value) financials.set(target.symbol, value);
|
|
1283
1285
|
});
|
|
1284
1286
|
return financials;
|
|
@@ -1298,7 +1300,7 @@ async function loadPerpetualFunding(tickers: readonly TickerRecord[]): Promise<M
|
|
|
1298
1300
|
});
|
|
1299
1301
|
const funding = new Map<string, number | null>();
|
|
1300
1302
|
await mapWithConcurrency(markets, PERPETUAL_CONCURRENCY, async ({ symbol, listing }) => {
|
|
1301
|
-
funding.set(symbol, await loadPerpetualMarket(listing).then((market) => market?.row.fundingRate8h ?? null, (
|
|
1303
|
+
funding.set(symbol, await loadPerpetualMarket(listing).then((market) => market?.row.fundingRate8h ?? null, logFailure("portfolio-list.cli.render", "Load perpetual funding failed", null)));
|
|
1302
1304
|
});
|
|
1303
1305
|
return funding;
|
|
1304
1306
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useEffect, useMemo } from "react";
|
|
2
|
+
import { getSharedMarketDataCoordinator } from "../../../../market-data/coordinator";
|
|
3
|
+
import { formatMarketPrice, quoteFormatOptions, withCurrencyMinorDigits } from "../../../../market-data/market/format";
|
|
4
|
+
import { buildQuoteKey, instrumentFromQuoteTarget, resolveEntryData } from "../../../../market-data/selectors";
|
|
5
|
+
import type { PortfolioFuturesTerms } from "../../../../portfolio/futures-positions";
|
|
6
|
+
import { useLiveQuoteEntries } from "../../../../state/hooks/quote-streaming";
|
|
7
|
+
import type { QuoteSubscriptionTarget } from "../../../../types/data-provider";
|
|
8
|
+
import { logFailure } from "../../../../utils/log-failure";
|
|
9
|
+
|
|
10
|
+
/** Front references share one snapshot and stream per contract, independent of each row's own price. */
|
|
11
|
+
export function useFuturesFrontNotes(
|
|
12
|
+
terms: ReadonlyMap<string, PortfolioFuturesTerms>,
|
|
13
|
+
{ enabled, liveStreaming }: { enabled: boolean; liveStreaming: boolean },
|
|
14
|
+
): ReadonlyMap<string, string> {
|
|
15
|
+
const symbolsKey = useMemo(() => enabled
|
|
16
|
+
? [...new Set([...terms.values()].flatMap((value) => value.front?.contractSymbol ?? []))].sort().join("\u001f") : "", [enabled, terms]);
|
|
17
|
+
const targets = useMemo<QuoteSubscriptionTarget[]>(() => symbolsKey ? symbolsKey.split("\u001f").map((symbol) => ({
|
|
18
|
+
symbol, exchange: "", route: "provider", surface: "watchlist", visible: true,
|
|
19
|
+
})) : [], [symbolsKey]);
|
|
20
|
+
const instruments = useMemo(() => targets.map(instrumentFromQuoteTarget), [targets]);
|
|
21
|
+
const coordinator = getSharedMarketDataCoordinator();
|
|
22
|
+
const { entries } = useLiveQuoteEntries(targets, { liveStreaming });
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
// Polling primes its own batch immediately; a stream needs one snapshot while connecting.
|
|
25
|
+
if (!liveStreaming || !coordinator || !instruments.length) return;
|
|
26
|
+
void coordinator.loadQuotesBatch(instruments).catch(logFailure("portfolio-futures", "Read front contract quotes failed"));
|
|
27
|
+
}, [coordinator, instruments, liveStreaming]);
|
|
28
|
+
return useMemo(() => {
|
|
29
|
+
const notes = new Map<string, string>();
|
|
30
|
+
if (!enabled) return notes;
|
|
31
|
+
for (const [symbol, value] of terms) {
|
|
32
|
+
if (!value.front) continue;
|
|
33
|
+
const quote = resolveEntryData(entries.get(buildQuoteKey({ symbol: value.front.contractSymbol, exchange: "" })));
|
|
34
|
+
if (!quote || !Number.isFinite(quote.price)) continue;
|
|
35
|
+
const options = withCurrencyMinorDigits(quoteFormatOptions(quote, "FUTURE"), quote.currency);
|
|
36
|
+
notes.set(symbol, `front ${value.front.contract} ${formatMarketPrice(quote.price, options)}`);
|
|
37
|
+
}
|
|
38
|
+
return notes;
|
|
39
|
+
}, [enabled, entries, terms]);
|
|
40
|
+
}
|
|
@@ -104,6 +104,7 @@ import { hasUnknownOptionMultiplier } from "../../../../portfolio/position-metri
|
|
|
104
104
|
import { withFuturesContractSizes } from "../../../../portfolio/futures-positions";
|
|
105
105
|
import { withPurchaseDateCostFx } from "../../../../portfolio/cost-fx";
|
|
106
106
|
import { usePortfolioFuturesTerms } from "./futures-terms";
|
|
107
|
+
import { useFuturesFrontNotes } from "./futures-front";
|
|
107
108
|
import { usePurchaseDateCostFx } from "./purchase-date-fx";
|
|
108
109
|
import {
|
|
109
110
|
allocationWeightBase,
|
|
@@ -185,7 +186,8 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
|
|
|
185
186
|
[config.baseCurrency, currentPortfolio],
|
|
186
187
|
);
|
|
187
188
|
// A future counts contracts of the size its terms give, and at the close is valued at its settlement.
|
|
188
|
-
const futuresTerms = usePortfolioFuturesTerms(collectionTickers, { enabled:
|
|
189
|
+
const futuresTerms = usePortfolioFuturesTerms(collectionTickers, { enabled: true, settlement: isPortfolioTab && valuationBasis === "regular-close" });
|
|
190
|
+
const futuresFrontNotes = useFuturesFrontNotes(futuresTerms.terms, { enabled: appActive && viewMode === "table", liveStreaming });
|
|
189
191
|
// A dated lot in another currency costs what its purchase date's FX close says in that currency, as portfolio show values it.
|
|
190
192
|
const purchaseDateFx = usePurchaseDateCostFx(collectionTickers, { enabled: isPortfolioTab, portfolioId: activeCollectionId, into: totalsCurrency });
|
|
191
193
|
const tickers = useMemo(
|
|
@@ -796,6 +798,7 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
|
|
|
796
798
|
onCursorChange={handleCursorChange}
|
|
797
799
|
financialsMap={financialsMap}
|
|
798
800
|
columnContext={columnContext}
|
|
801
|
+
futuresFrontNotes={futuresFrontNotes}
|
|
799
802
|
flashSymbols={flashSymbols}
|
|
800
803
|
sinceLastSeen={sinceLastSeen}
|
|
801
804
|
onRootKeyDown={handleTableKeyDown}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../../utils/log-failure";
|
|
1
2
|
import { useEffect, useMemo, useRef } from "react";
|
|
2
3
|
import { getSharedMarketDataCoordinator } from "../../../../market-data/coordinator";
|
|
3
4
|
import { instrumentFromTicker, quoteSubscriptionTargetFromTicker } from "../../../../market-data/request-types";
|
|
@@ -251,8 +252,9 @@ export function usePortfolioPaneStreaming({
|
|
|
251
252
|
? sharedCoordinator.loadSnapshotsBatch(normalSnapshotEntries.map((entry) => entry.instrument))
|
|
252
253
|
: Promise.resolve(),
|
|
253
254
|
]);
|
|
254
|
-
} catch {
|
|
255
|
+
} catch (error) {
|
|
255
256
|
// Best-effort warmup for visible rows only.
|
|
257
|
+
logFailure("portfolio-list.pane.streaming", "Warm visible portfolio rows failed")(error);
|
|
256
258
|
} finally {
|
|
257
259
|
for (const entry of [...quoteEntries, ...forcedSnapshotEntries, ...normalSnapshotEntries]) {
|
|
258
260
|
warmupInFlightRef.current.delete(entry.key);
|
|
@@ -304,8 +306,9 @@ export function usePortfolioPaneStreaming({
|
|
|
304
306
|
|
|
305
307
|
try {
|
|
306
308
|
await sharedCoordinator.loadQuotesBatch(quoteEntries.map((entry) => entry.instrument), { forceRefresh: true });
|
|
307
|
-
} catch {
|
|
309
|
+
} catch (error) {
|
|
308
310
|
// Best-effort watchdog for visible rows only.
|
|
311
|
+
logFailure("portfolio-list.pane.streaming", "Refresh visible portfolio quotes failed")(error);
|
|
309
312
|
} finally {
|
|
310
313
|
for (const entry of quoteEntries) {
|
|
311
314
|
warmupInFlightRef.current.delete(entry.key);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../../utils/log-failure";
|
|
1
2
|
import { useEffect, useMemo, useRef, useState, type Dispatch, type SetStateAction } from "react";
|
|
2
3
|
import type { ColumnConfig } from "../../../../types/config";
|
|
3
4
|
import type { AnalystResearchData, CorporateActionsData, TickerFinancials } from "../../../../types/financials";
|
|
@@ -171,7 +172,7 @@ function useAnalystSupplemental(
|
|
|
171
172
|
}
|
|
172
173
|
})).then((entries) => {
|
|
173
174
|
if (mountedRef.current) setState((current) => setAnalystValues(current, entries));
|
|
174
|
-
}).catch((
|
|
175
|
+
}).catch(logFailure("portfolio-list.pane.supplemental", "Apply analyst research failed"));
|
|
175
176
|
}, [enabled, mountedRef, provider, setState, state.analystResearch, targetsKey]);
|
|
176
177
|
}
|
|
177
178
|
|
|
@@ -211,7 +212,7 @@ function useCorporateSupplemental(
|
|
|
211
212
|
}
|
|
212
213
|
})).then((entries) => {
|
|
213
214
|
if (mountedRef.current) setState((current) => setCorporateValues(current, entries));
|
|
214
|
-
}).catch((
|
|
215
|
+
}).catch(logFailure("portfolio-list.pane.supplemental", "Apply corporate actions failed"));
|
|
215
216
|
}, [enabled, mountedRef, provider, setState, state.corporateActions, targetsKey]);
|
|
216
217
|
}
|
|
217
218
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../../utils/log-failure";
|
|
1
2
|
import { getSharedRegistry } from "../../../registry";
|
|
2
3
|
import {
|
|
3
4
|
AmbiguousContractError,
|
|
@@ -178,7 +179,8 @@ export async function resolveQuickAddValidation({
|
|
|
178
179
|
exchangeFromResolved(resolved),
|
|
179
180
|
quoteContextFromResolved(resolved),
|
|
180
181
|
);
|
|
181
|
-
} catch {
|
|
182
|
+
} catch (error) {
|
|
183
|
+
logFailure("portfolio-list.quick-add.resolution", "Read quick-add quote failed")(error);
|
|
182
184
|
quote = null;
|
|
183
185
|
}
|
|
184
186
|
}
|
|
@@ -111,6 +111,7 @@ export function PortfolioTickerTable({
|
|
|
111
111
|
onCursorChange,
|
|
112
112
|
financialsMap,
|
|
113
113
|
columnContext,
|
|
114
|
+
futuresFrontNotes,
|
|
114
115
|
flashSymbols,
|
|
115
116
|
sinceLastSeen,
|
|
116
117
|
onRootKeyDown,
|
|
@@ -134,6 +135,7 @@ export function PortfolioTickerTable({
|
|
|
134
135
|
onCursorChange?: (ticker: TickerRecord, index: number) => void;
|
|
135
136
|
financialsMap: Map<string, TickerFinancials>;
|
|
136
137
|
columnContext: ColumnContext;
|
|
138
|
+
futuresFrontNotes?: ReadonlyMap<string, string>;
|
|
137
139
|
flashSymbols: Map<string, QuoteFlashDirection>;
|
|
138
140
|
/** For a few seconds after coming back to the pane, LAST is tinted by each row's move since the user left. */
|
|
139
141
|
sinceLastSeen?: SinceLastSeenRecap | null;
|
|
@@ -155,21 +157,26 @@ export function PortfolioTickerTable({
|
|
|
155
157
|
openPerpetualRef.current = onOpenPerpetual;
|
|
156
158
|
const sparklineVisible = columns.some((column) => column.id === PRICE_SPARKLINE_COLUMN_ID);
|
|
157
159
|
const tableFinancialsMap = useLiveSparklineFinancials(financialsMap, sortedTickers, sparklineVisible);
|
|
160
|
+
const noteColumnId = columns.some((column) => column.id === "name") ? "name" : "ticker";
|
|
161
|
+
const hasFrontReference = useMemo(() => sortedTickers.some((ticker) => columnContext.futuresTerms?.get(ticker.metadata.ticker)?.front),
|
|
162
|
+
[columnContext.futuresTerms, sortedTickers]);
|
|
158
163
|
const resolveCell = useCallback(
|
|
159
164
|
(column: ColumnConfig, ticker: TickerRecord, financials: TickerFinancials | undefined) => {
|
|
160
165
|
const key = `${ticker.metadata.ticker}:${column.id}`;
|
|
161
166
|
const version = buildCellVersion(column, ticker, financials, columnContext);
|
|
162
|
-
const
|
|
167
|
+
const cached = cellCacheRef.current.get(key, version, () => {
|
|
163
168
|
const cell = getColumnValue(column, ticker, financials, columnContext);
|
|
164
169
|
const marketId = PERPETUAL_COLUMN_IDS.has(column.id) ? columnContext.perpetuals?.get(ticker.metadata.ticker)?.marketId : undefined;
|
|
165
170
|
return marketId ? { ...cell, onPress: () => openPerpetualRef.current?.(marketId) } : cell;
|
|
166
171
|
});
|
|
172
|
+
const note = column.id === noteColumnId ? futuresFrontNotes?.get(ticker.metadata.ticker) : undefined;
|
|
173
|
+
const cell = note ? { ...cached, note } : cached;
|
|
167
174
|
// LAST keeps its price: a move since the user last looked only tints the cell.
|
|
168
175
|
if (column.id !== "price" || !sinceLastSeen) return cell;
|
|
169
176
|
const move = sinceLastSeen.moves.get(ticker.metadata.ticker);
|
|
170
177
|
return move == null ? cell : { ...cell, backgroundColor: sinceLastSeenTint(move, sinceLastSeen.fading) };
|
|
171
178
|
},
|
|
172
|
-
[columnContext, sinceLastSeen],
|
|
179
|
+
[columnContext, futuresFrontNotes, noteColumnId, sinceLastSeen],
|
|
173
180
|
);
|
|
174
181
|
const pnlColumn = columns.find((column) => column.id === "pnl" || column.id === "pnl_pct");
|
|
175
182
|
// Reads the same cached P&L cells the rows show; only changed rows recompute.
|
|
@@ -202,7 +209,8 @@ export function PortfolioTickerTable({
|
|
|
202
209
|
: pnlLabel !== "P&L" && (column.id === "pnl" || column.id === "pnl_pct") ? {
|
|
203
210
|
...column,
|
|
204
211
|
label: column.id === "pnl_pct" ? pnlLabel.replace("P&L", "%") : pnlLabel,
|
|
205
|
-
} : column)
|
|
212
|
+
} : column).map((column) => hasFrontReference && column.id === noteColumnId ? { ...column, flexGrow: 1 } : column),
|
|
213
|
+
[columns, grossWeights, hasFrontReference, hasNonShareQuantity, noteColumnId, pnlLabel, sharesWidth, tickerWidth]);
|
|
206
214
|
// The table offers "Sort by…" while it has a sort column. A watchlist starts
|
|
207
215
|
// unsorted and a third header click turns sorting off, so the unsorted state
|
|
208
216
|
// needs its own entry until TickerListTableView takes `sortable`.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createTtlCache } from "../../../data/ttl-cache";
|
|
1
2
|
import { httpFetch } from "../../../utils/http-transport";
|
|
2
3
|
import { isoDateOrNull, numericOrNull, recordOrNull, textOrNull } from "../../../utils/guards";
|
|
3
4
|
import {
|
|
@@ -84,20 +85,14 @@ async function readJson(url: string): Promise<unknown> {
|
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
/** The series break changes about every two years; one read serves the session. */
|
|
87
|
-
|
|
88
|
+
const seriesBreaks = createTtlCache<string>({ ttlMs: Infinity });
|
|
88
89
|
|
|
89
90
|
function currentSeriesBreak(force: boolean): Promise<string> {
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
});
|
|
96
|
-
seriesBreakRead = read;
|
|
97
|
-
// A failed read is not kept, so the next one asks again.
|
|
98
|
-
read.catch(() => { if (seriesBreakRead === read) seriesBreakRead = null; });
|
|
99
|
-
}
|
|
100
|
-
return seriesBreakRead;
|
|
91
|
+
return seriesBreaks.load("current", async () => {
|
|
92
|
+
const seriesBreak = newestSeriesBreak(parseSeriesBreaks(await readJson(`${API}/list/seriesbreaks.json`)));
|
|
93
|
+
if (!seriesBreak) throw new Error("No series break was listed.");
|
|
94
|
+
return seriesBreak;
|
|
95
|
+
}, { force });
|
|
101
96
|
}
|
|
102
97
|
|
|
103
98
|
/**
|
|
@@ -117,31 +112,11 @@ export async function fetchPrimaryDealers(): Promise<DealerBoard> {
|
|
|
117
112
|
};
|
|
118
113
|
}
|
|
119
114
|
|
|
120
|
-
|
|
121
|
-
at: number;
|
|
122
|
-
read: Promise<SeriesPoint[]>;
|
|
123
|
-
points: SeriesPoint[] | null;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const seriesReads = new Map<string, SeriesRead>();
|
|
115
|
+
const seriesReads = createTtlCache<SeriesPoint[]>({ ttlMs: HISTORY_TTL_MS });
|
|
127
116
|
|
|
128
117
|
function seriesRead(seriesBreak: string, key: string, force: boolean): Promise<SeriesPoint[]> {
|
|
129
|
-
const id = `${seriesBreak}:${key}`;
|
|
130
|
-
const known = seriesReads.get(id);
|
|
131
|
-
if (known && !force && Date.now() - known.at < HISTORY_TTL_MS) return known.read;
|
|
132
118
|
const url = `${API}/get/${encodeURIComponent(seriesBreak)}/timeseries/${encodeURIComponent(key)}.json`;
|
|
133
|
-
|
|
134
|
-
at: Date.now(),
|
|
135
|
-
points: null,
|
|
136
|
-
read: readJson(url).then((payload) => {
|
|
137
|
-
entry.points = parseSeriesPoints(payload);
|
|
138
|
-
return entry.points;
|
|
139
|
-
}),
|
|
140
|
-
};
|
|
141
|
-
seriesReads.set(id, entry);
|
|
142
|
-
// A failed read is not kept, so the next one asks again.
|
|
143
|
-
entry.read.catch(() => { if (seriesReads.get(id) === entry) seriesReads.delete(id); });
|
|
144
|
-
return entry.read;
|
|
119
|
+
return seriesReads.load(`${seriesBreak}:${key}`, async () => parseSeriesPoints(await readJson(url)), { force });
|
|
145
120
|
}
|
|
146
121
|
|
|
147
122
|
/** A row's history, one read per series behind it, each kept for the session. */
|
|
@@ -158,9 +133,9 @@ export async function loadDealerHistory(
|
|
|
158
133
|
function heldSeries(seriesBreak: string, row: Pick<DealerRow, "keys">): SeriesPoint[][] | null {
|
|
159
134
|
const series: SeriesPoint[][] = [];
|
|
160
135
|
for (const key of row.keys) {
|
|
161
|
-
const
|
|
162
|
-
if (!
|
|
163
|
-
series.push(
|
|
136
|
+
const points = seriesReads.peek(`${seriesBreak}:${key}`);
|
|
137
|
+
if (!points) return null;
|
|
138
|
+
series.push(points);
|
|
164
139
|
}
|
|
165
140
|
return series;
|
|
166
141
|
}
|
|
@@ -178,6 +153,6 @@ export function cachedDealerHistory(seriesBreak: string, row: Pick<DealerRow, "i
|
|
|
178
153
|
|
|
179
154
|
/** Forgets what was read, for a test or a refresh that must see the release again. */
|
|
180
155
|
export function resetPrimaryDealersCache(): void {
|
|
181
|
-
|
|
156
|
+
seriesBreaks.clear();
|
|
182
157
|
seriesReads.clear();
|
|
183
158
|
}
|
|
@@ -21,13 +21,13 @@ export const realizedVolModule: PluginModule = {
|
|
|
21
21
|
{ ...createTickerSurfacePaneTemplate({
|
|
22
22
|
id: "realized-vol-graph-pane", paneId: "realized-vol", label: "Realized Volatility Graph",
|
|
23
23
|
description: "Rolling realized volatility, price and a dated current ATM IV reference.",
|
|
24
|
-
keywords: ["hvg", "realized", "historical", "volatility", "gv"], shortcut: "HVG", publicShare: true,
|
|
24
|
+
keywords: ["hvg", "realized", "historical", "volatility", "vol", "gv"], shortcut: "HVG", publicShare: true,
|
|
25
25
|
viewKey: "graph", settings: () => ({ initialView: "graph" }),
|
|
26
26
|
}), headless: realizedVolHeadless("graph") },
|
|
27
27
|
{ ...createTickerSurfacePaneTemplate({
|
|
28
28
|
id: "realized-vol-table-pane", paneId: "realized-vol", label: "Realized Volatility Cone",
|
|
29
29
|
description: "Current realized volatility against its historical range and percentile.",
|
|
30
|
-
keywords: ["hvt", "volatility", "cone", "percentile"], shortcut: "HVT", publicShare: true,
|
|
30
|
+
keywords: ["hvt", "volatility", "vol", "cone", "percentile"], shortcut: "HVT", publicShare: true,
|
|
31
31
|
viewKey: "cone", settings: () => ({ initialView: "cone" }),
|
|
32
32
|
}), headless: realizedVolHeadless("cone") },
|
|
33
33
|
],
|