@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
|
@@ -48,7 +48,16 @@ import {
|
|
|
48
48
|
type RiskHoldingRequest,
|
|
49
49
|
type RiskMarketSnapshot,
|
|
50
50
|
} from "./risk-client";
|
|
51
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
accountFxPair,
|
|
53
|
+
accountFxReason,
|
|
54
|
+
bridgedFxClose,
|
|
55
|
+
convertReturnsToAccount,
|
|
56
|
+
FX_UNAVAILABLE,
|
|
57
|
+
fxBridgeNote,
|
|
58
|
+
riskConversion,
|
|
59
|
+
type AccountFx,
|
|
60
|
+
} from "./risk-fx";
|
|
52
61
|
|
|
53
62
|
interface PortfolioRiskHolding {
|
|
54
63
|
id: string;
|
|
@@ -77,6 +86,8 @@ interface PortfolioRiskHolding {
|
|
|
77
86
|
historyAsOf: string | null;
|
|
78
87
|
/** Daily returns in the account currency. */
|
|
79
88
|
returns: DatedReturn[];
|
|
89
|
+
/** Dates its returns converted at the FX close before them, by pair ("GBP/USD"), its daily closes having none (bridgedFxClose). */
|
|
90
|
+
fxBridged: ReadonlyMap<string, readonly string[]>;
|
|
80
91
|
error: string | null;
|
|
81
92
|
/** Why the holding is outside the basket; null when it qualifies. */
|
|
82
93
|
leftOut: string | null;
|
|
@@ -386,15 +397,43 @@ function cashReason(line: RiskCashLine, accountCurrency: string): string {
|
|
|
386
397
|
? "Cash is outside the equity basket"
|
|
387
398
|
: `Cash is outside the equity basket; its ${accountCurrency} value moves with ${own}`;
|
|
388
399
|
}
|
|
400
|
+
/** One note per pair whose missing daily closes the basket's returns were converted across (fxBridgeNote). */
|
|
401
|
+
function fxBridgeNotes(members: readonly Pick<PortfolioRiskHolding, "fxBridged">[]): string[] {
|
|
402
|
+
const byPair = new Map<string, Set<string>>();
|
|
403
|
+
for (const member of members)
|
|
404
|
+
for (const [pair, dates] of member.fxBridged) {
|
|
405
|
+
const set = byPair.get(pair) ?? new Set<string>();
|
|
406
|
+
for (const date of dates) set.add(date);
|
|
407
|
+
byPair.set(pair, set);
|
|
408
|
+
}
|
|
409
|
+
return [...byPair].flatMap(([pair, dates]) => fxBridgeNote(pair, dates) ?? []);
|
|
410
|
+
}
|
|
389
411
|
/** USD returns in the account currency, or none with the reason its daily closes cannot convert them. */
|
|
390
|
-
function accountReturns(
|
|
391
|
-
|
|
412
|
+
function accountReturns(
|
|
413
|
+
returns: readonly DatedReturn[],
|
|
414
|
+
account: AccountFx,
|
|
415
|
+
): { returns: DatedReturn[]; bridged: string[]; error: string | null } {
|
|
416
|
+
if (account.error) return { returns: [], bridged: [], error: account.error };
|
|
392
417
|
try {
|
|
393
|
-
return {
|
|
418
|
+
return { ...convertReturnsToAccount(returns, account), error: null };
|
|
394
419
|
} catch (error) {
|
|
395
|
-
return { returns: [], error: error instanceof Error ? error.message : accountFxReason(account.currency, FX_UNAVAILABLE) };
|
|
420
|
+
return { returns: [], bridged: [], error: error instanceof Error ? error.message : accountFxReason(account.currency, FX_UNAVAILABLE) };
|
|
396
421
|
}
|
|
397
422
|
}
|
|
423
|
+
/** The dates a holding's returns converted at the FX close before them, by pair: its listing's into USD, then USD into the account's. */
|
|
424
|
+
function holdingFxBridges(
|
|
425
|
+
listingCurrency: string | null,
|
|
426
|
+
listingBridged: readonly string[] | undefined,
|
|
427
|
+
account: AccountFx | null | undefined,
|
|
428
|
+
accountBridged: readonly string[] | undefined,
|
|
429
|
+
): Map<string, readonly string[]> {
|
|
430
|
+
const bridges = new Map<string, readonly string[]>();
|
|
431
|
+
const listingPair = listingCurrency && listingBridged?.length ? accountFxPair(riskConversion(listingCurrency)?.currency ?? listingCurrency) : null;
|
|
432
|
+
if (listingPair) bridges.set(listingPair, listingBridged!);
|
|
433
|
+
const accountPair = account?.pair;
|
|
434
|
+
if (accountPair && accountBridged?.length) bridges.set(accountPair, [...(bridges.get(accountPair) ?? []), ...accountBridged]);
|
|
435
|
+
return bridges;
|
|
436
|
+
}
|
|
398
437
|
/**
|
|
399
438
|
* "returns in TRY at daily USD/TRY closes": the currency the basket views of a
|
|
400
439
|
* non-USD account are in and how they got there; null for a USD account, or
|
|
@@ -474,6 +513,8 @@ export function riskCoverageNotices(coverage: RiskCoverage | undefined): string[
|
|
|
474
513
|
`${row.symbol} (${row.share == null ? "value unknown" : `${shareText(row.share)} of the book`}) is excluded from the risk estimate: ${lowerFirst(row.reason.replace(/\.$/, ""))}.`,
|
|
475
514
|
);
|
|
476
515
|
}
|
|
516
|
+
/** What an empty portfolio's pane says after "No holdings in this portfolio.": where holdings come from. */
|
|
517
|
+
export const NO_HOLDINGS_HINT = "Add holdings from the Portfolio pane (PF), or connect a broker in BR to sync them.";
|
|
477
518
|
/** Why the basket views show no estimate, when too little of the holdings qualifies. */
|
|
478
519
|
export function riskCoverageShortfall(
|
|
479
520
|
coverage: RiskCoverage | undefined,
|
|
@@ -690,6 +731,7 @@ export function buildPortfolioRisk(
|
|
|
690
731
|
// Converted returns end with the account currency's last daily close.
|
|
691
732
|
historyAsOf: (inAccount ? inAccount.returns.at(-1)?.dateKey : null) ?? history?.asOf ?? null,
|
|
692
733
|
returns,
|
|
734
|
+
fxBridged: holdingFxBridges(convertedFrom, history?.converted?.fxBridged, account, inAccount?.bridged),
|
|
693
735
|
error,
|
|
694
736
|
leftOut,
|
|
695
737
|
};
|
|
@@ -774,6 +816,8 @@ export function buildPortfolioRisk(
|
|
|
774
816
|
"Converted holdings are matched to the US close of the same date; where their market closes at another time, correlations and betas to US holdings read lower than they are.",
|
|
775
817
|
]
|
|
776
818
|
: []),
|
|
819
|
+
// A pair's daily closes skip Good Friday, Easter Monday, 25 Dec and 1 Jan; returns across them use the close before.
|
|
820
|
+
...fxBridgeNotes(members),
|
|
777
821
|
];
|
|
778
822
|
const warnings = [
|
|
779
823
|
...market.warnings,
|
|
@@ -796,12 +840,14 @@ export function buildPortfolioRisk(
|
|
|
796
840
|
return (instrument && byId.get(riskInstrumentId(instrument))?.returns) || [];
|
|
797
841
|
};
|
|
798
842
|
// SPY's USD returns are the market factor and the equity stress; the risk
|
|
799
|
-
// rows compare the basket with SPY in the basket's own currency
|
|
800
|
-
//
|
|
801
|
-
//
|
|
843
|
+
// rows compare the basket with SPY in the basket's own currency, bridged
|
|
844
|
+
// across missing account closes as the holdings are (bridgedFxClose). A
|
|
845
|
+
// session past a longer gap is a gap in that benchmark, so it cannot blank
|
|
846
|
+
// the holdings the closes do cover.
|
|
802
847
|
const spy = proxy(RISK_BENCHMARK);
|
|
848
|
+
const accountClose = (date: string) => account ? bridgedFxClose((day) => account.closes[day], date) : null;
|
|
803
849
|
const benchmark = account
|
|
804
|
-
? accountReturns(spy.filter((point) =>
|
|
850
|
+
? accountReturns(spy.filter((point) => accountClose(point.startDateKey) && accountClose(point.dateKey)), account).returns
|
|
805
851
|
: spy;
|
|
806
852
|
// Since the filing only for a portfolio that has one; any other keeps the trailing window and says why.
|
|
807
853
|
const filed = window.since === "filed" ? portfolio.importedFrom?.filed ?? null : null;
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
useAsyncResource,
|
|
31
31
|
useAutoRefresh,
|
|
32
32
|
usePaneSettingValue,
|
|
33
|
+
usePluginAppActions,
|
|
33
34
|
usePluginPaneState,
|
|
34
35
|
useShortcut,
|
|
35
36
|
} from "../../../public/react";
|
|
@@ -54,6 +55,7 @@ import {
|
|
|
54
55
|
HOLDINGS_SHARE_UNIT,
|
|
55
56
|
HOLDINGS_SUMMARY_ROW_IDS,
|
|
56
57
|
isShockRow,
|
|
58
|
+
NO_HOLDINGS_HINT,
|
|
57
59
|
portfolioRiskRequests,
|
|
58
60
|
portfolioRiskTickers,
|
|
59
61
|
riskCoverageLine,
|
|
@@ -234,6 +236,7 @@ function RiskDetail({
|
|
|
234
236
|
export function PortfolioRiskPane({ focused, width, height }: PaneProps) {
|
|
235
237
|
const instance = usePaneInstance(),
|
|
236
238
|
renderer = useRendererHost();
|
|
239
|
+
const { showPane } = usePluginAppActions();
|
|
237
240
|
const portfolios = useAppSelector((state) => state.config.portfolios),
|
|
238
241
|
tickers = useAppSelector((state) => state.tickers),
|
|
239
242
|
brokerAccounts = useAppSelector((state) => state.brokerAccounts);
|
|
@@ -403,14 +406,16 @@ export function PortfolioRiskPane({ focused, width, height }: PaneProps) {
|
|
|
403
406
|
const model = derived.model;
|
|
404
407
|
const marketLoading = resource.loading && !resource.data;
|
|
405
408
|
// A basket view with nothing to estimate says why in the body, not with a table of dashes.
|
|
406
|
-
const basketBody = useMemo<{ loading?: boolean; error?: string; title?: string; message?: string } | null>(() => {
|
|
409
|
+
const basketBody = useMemo<{ loading?: boolean; error?: string; title?: string; message?: string; noHoldings?: boolean } | null>(() => {
|
|
407
410
|
if (!model || !BASKET_VIEWS.has(view)) return null;
|
|
408
411
|
if (!frozen && !resource.data)
|
|
409
412
|
return marketLoading ? { loading: true } : resource.error ? { error: resource.error } : null;
|
|
410
413
|
if (view === "holdings") return null;
|
|
411
414
|
const shortfall = riskCoverageShortfall(model.coverage);
|
|
412
415
|
// The currency and price shocks reprice the book without the basket, so they stay; the footer says why the basket scenarios do not.
|
|
413
|
-
|
|
416
|
+
if (shortfall && view === "stress" && model.rows.stress.some(isShockRow)) return null;
|
|
417
|
+
// A portfolio with no holdings says where they come from, as the other panes' empty states do.
|
|
418
|
+
return shortfall && !model.coverage?.holdings ? { ...shortfall, message: NO_HOLDINGS_HINT, noHoldings: true } : shortfall;
|
|
414
419
|
}, [frozen, marketLoading, model, resource.data, resource.error, view]);
|
|
415
420
|
// Stress with too little covered for the basket scenarios, its shocks shown alone.
|
|
416
421
|
const stressShortfall = useMemo(
|
|
@@ -816,6 +821,7 @@ export function PortfolioRiskPane({ focused, width, height }: PaneProps) {
|
|
|
816
821
|
loadingLabel="Loading portfolio risk"
|
|
817
822
|
emptyTitle={basketBody.title}
|
|
818
823
|
emptyMessage={basketBody.message || undefined}
|
|
824
|
+
actions={basketBody.noHoldings ? <Button label="Open Portfolio" compact onPress={() => showPane("portfolio-list")} /> : undefined}
|
|
819
825
|
/>
|
|
820
826
|
) : evidenceMissing ? (
|
|
821
827
|
<PaneStatusBody
|
|
@@ -5,11 +5,12 @@ import { attentionHeadless } from "./headless";
|
|
|
5
5
|
import { TrendingPane } from "./trending";
|
|
6
6
|
import { AttentionPane } from "./pane";
|
|
7
7
|
import { ATTENTION_SERIES } from "./series";
|
|
8
|
+
import { ATTENTION_LISTED } from "./listing";
|
|
8
9
|
export const attentionModule: PluginModule = {
|
|
9
10
|
panes: [{ id: "attention", name: "Research Attention", icon: "A", component: AttentionPane, defaultPosition: "right", defaultMode: "floating",
|
|
10
11
|
defaultFloatingSize: { width: 126, height: 32 }, tableExport: true, headless: attentionHeadless },
|
|
11
12
|
{ id: "attention-trending", reportFreshness: { status: "not-a-feed", basis: "hourly publication" }, name: "Gloom Trending", icon: "A", component: TrendingPane, defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 58, height: 12 } }],
|
|
12
|
-
paneTemplates: [{ id: "attention-pane", paneId: "attention", label: "Research Attention",
|
|
13
|
+
paneTemplates: [{ id: "attention-pane", paneId: "attention", label: "Research Attention", listed: ATTENTION_LISTED,
|
|
13
14
|
description: "Pro research attention: rankings, abnormal activity, sectors, countries and privacy-qualified hourly history.",
|
|
14
15
|
keywords: ["attention", "trending", "research", "activity"], shortcut: { prefix: "ATTN", argKind: "text", argPlaceholder: "TICKER", argOptional: true },
|
|
15
16
|
headless: attentionHeadless, createInstance: (_ctx, options) => ({ placement: "floating", settings: { window: attentionWindow(options?.values?.window), symbol: (options?.arg ?? options?.symbol)?.trim().toUpperCase() ?? "" } }) },
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether ATTN is listed: in command-bar search, Help, `gloom catalog` and
|
|
3
|
+
* search, the plugin directory and chat suggestions, and its two screener
|
|
4
|
+
* fields (ATTN HRS, ATTN Z) in the EQS field and metric pickers.
|
|
5
|
+
*
|
|
6
|
+
* Research attention publishes an hour only once enough opted-in users
|
|
7
|
+
* researched in it, and it has published nothing for weeks, so ATTN stays
|
|
8
|
+
* unlisted until it does. Typing ATTN still opens it, a screen that already
|
|
9
|
+
* uses its fields keeps them, and the Attention Counts setting and its
|
|
10
|
+
* collection are unchanged. Set it to true the day ATTN publishes again,
|
|
11
|
+
* with the server's own switch, and refresh the docs snapshot
|
|
12
|
+
* (`bun run docs:snapshot`) once the site lists ATTN.
|
|
13
|
+
*/
|
|
14
|
+
export const ATTENTION_LISTED = false;
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
resetSignedInBrokerCatalog,
|
|
12
12
|
subscribeSignedInBrokers,
|
|
13
13
|
} from "../../../brokers/signed-in/catalog";
|
|
14
|
-
import { DataTableStackView, EmptyState } from "../../../components";
|
|
14
|
+
import { Button, DataTableStackView, EmptyState } from "../../../components";
|
|
15
15
|
import { t } from "../../../i18n";
|
|
16
16
|
import { useAppLanguage } from "../../../i18n/react";
|
|
17
17
|
import {
|
|
@@ -340,10 +340,14 @@ export function BrokersPane({ focused, width, height }: PaneProps) {
|
|
|
340
340
|
sortDirection="asc"
|
|
341
341
|
getItemKey={(row) => row.id}
|
|
342
342
|
renderCell={renderCell}
|
|
343
|
-
// The footer's a
|
|
343
|
+
// The same add the footer's a and the command bar start; as the state's first
|
|
344
|
+
// action it also answers Enter.
|
|
344
345
|
emptyContent={(
|
|
345
346
|
<Box width="100%" paddingX={1} paddingY={1}>
|
|
346
|
-
<EmptyState
|
|
347
|
+
<EmptyState
|
|
348
|
+
title={t("No broker profiles.")}
|
|
349
|
+
actions={<Button label="Add Broker Account" onPress={startAdd} />}
|
|
350
|
+
/>
|
|
347
351
|
</Box>
|
|
348
352
|
)}
|
|
349
353
|
emptyStateTitle={t("No broker profiles.")}
|
|
@@ -12,11 +12,19 @@ import {
|
|
|
12
12
|
activityLabel,
|
|
13
13
|
criticalityColor,
|
|
14
14
|
metricColor,
|
|
15
|
+
moneyAmount,
|
|
16
|
+
moneyText,
|
|
15
17
|
text,
|
|
16
18
|
tickerSymbol,
|
|
17
19
|
} from "./format";
|
|
18
20
|
import { favoriteKey } from "./table-model";
|
|
19
21
|
|
|
22
|
+
/** A dollar figure in the compact form, with its whole number for an export. */
|
|
23
|
+
function moneyCell(value: string | null | undefined, color: string): DataTableCell {
|
|
24
|
+
const amount = moneyAmount(value);
|
|
25
|
+
return { text: moneyText(value), ...(amount == null ? {} : { value: amount }), color };
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
interface BuildoutCellContext {
|
|
21
29
|
favoriteBusyKey: string | null;
|
|
22
30
|
/** The row's last star did not take. */
|
|
@@ -40,7 +48,7 @@ export function renderBuildoutCell(
|
|
|
40
48
|
case "companyCount":
|
|
41
49
|
return { text: list.companyCount == null ? "-" : String(list.companyCount), color: colors.textDim };
|
|
42
50
|
case "totalMarketCap":
|
|
43
|
-
return
|
|
51
|
+
return moneyCell(list.totalMarketCap, colors.textDim);
|
|
44
52
|
case "avgSectorGrowth":
|
|
45
53
|
return { text: text(list.avgSectorGrowth), color: metricColor(list.avgSectorGrowth) };
|
|
46
54
|
case "avgReturn1y":
|
|
@@ -87,16 +95,16 @@ export function renderBuildoutCell(
|
|
|
87
95
|
case "criticality":
|
|
88
96
|
return { text: text(company.aiCriticality), color: criticalityColor(company.aiCriticality), attributes: TextAttributes.BOLD };
|
|
89
97
|
case "marketCap":
|
|
90
|
-
return
|
|
98
|
+
return moneyCell(company.marketCap, colors.textDim);
|
|
91
99
|
case "revenue":
|
|
92
|
-
return
|
|
100
|
+
return moneyCell(company.revenue, colors.textDim);
|
|
93
101
|
case "revenueGrowth":
|
|
94
102
|
return {
|
|
95
103
|
text: text(company.revenueGrowthYoy ?? company.lastQuarterGrowth),
|
|
96
104
|
color: metricColor(company.revenueGrowthYoy ?? company.lastQuarterGrowth),
|
|
97
105
|
};
|
|
98
106
|
case "netIncome":
|
|
99
|
-
return
|
|
107
|
+
return moneyCell(company.netIncome, metricColor(company.netIncome));
|
|
100
108
|
case "margin":
|
|
101
109
|
return { text: text(company.profitMargins), color: metricColor(company.profitMargins) };
|
|
102
110
|
case "forwardPE":
|
|
@@ -44,6 +44,44 @@ export function metricNumber(value: unknown): number | null {
|
|
|
44
44
|
return (negative ? -parsed : parsed) * multiplier;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/** A dollar figure as the service prints it ("$5094.1B", "$-4.9B"); anything else is not money to reformat. */
|
|
48
|
+
const DOLLAR_STRING = /^-?\$-?[\d,]*\.?\d+\s*[KMBT]?$/i;
|
|
49
|
+
|
|
50
|
+
/** The dollars in a string the service prints as money, or null for anything else. */
|
|
51
|
+
export function moneyAmount(value: unknown): number | null {
|
|
52
|
+
return typeof value === "string" && DOLLAR_STRING.test(value.trim()) ? metricNumber(value) : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const MONEY_UNITS = [
|
|
56
|
+
{ divisor: 1e12, suffix: "T" },
|
|
57
|
+
{ divisor: 1e9, suffix: "B" },
|
|
58
|
+
{ divisor: 1e6, suffix: "M" },
|
|
59
|
+
{ divisor: 1e3, suffix: "K" },
|
|
60
|
+
] as const;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Dollars in the one compact form every TheBuildout table uses: "$5.1T",
|
|
64
|
+
* "$356.4B", "-$4.9B". The unit is picked after rounding, so $999.96B is
|
|
65
|
+
* "$1.0T" and not "$1000.0B".
|
|
66
|
+
*/
|
|
67
|
+
export function formatCompactMoney(amount: number): string {
|
|
68
|
+
const abs = Math.abs(amount);
|
|
69
|
+
let index = MONEY_UNITS.findIndex((unit) => abs >= unit.divisor);
|
|
70
|
+
let digits = index < 0 ? abs.toFixed(0) : (abs / MONEY_UNITS[index]!.divisor).toFixed(1);
|
|
71
|
+
if (index > 0 && Number(digits) >= 1000) {
|
|
72
|
+
index -= 1;
|
|
73
|
+
digits = (abs / MONEY_UNITS[index]!.divisor).toFixed(1);
|
|
74
|
+
}
|
|
75
|
+
const sign = amount < 0 && /[1-9]/.test(digits) ? "-" : "";
|
|
76
|
+
return `${sign}$${digits}${index < 0 ? "" : MONEY_UNITS[index]!.suffix}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** A table cell's text for a dollar figure: compact when it is money, the service's own text otherwise, "-" when absent. */
|
|
80
|
+
export function moneyText(value: string | null | undefined): string {
|
|
81
|
+
const amount = moneyAmount(value);
|
|
82
|
+
return amount == null ? text(value) : formatCompactMoney(amount);
|
|
83
|
+
}
|
|
84
|
+
|
|
47
85
|
export function dateShort(value: string | null | undefined) {
|
|
48
86
|
const date = parseDisplayDate(value);
|
|
49
87
|
if (!date) return "-";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { metricNumber, textOrNull, tickerSymbol, uniqueStrings } from "../format";
|
|
1
|
+
import { metricNumber, moneyAmount, textOrNull, tickerSymbol, uniqueStrings } from "../format";
|
|
2
2
|
import type {
|
|
3
3
|
BuildoutCompany,
|
|
4
4
|
BuildoutList,
|
|
@@ -124,7 +124,42 @@ export function normalizeList(raw: RawObject): BuildoutList {
|
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* The statement figures a depositary receipt can carry in the issuer's
|
|
129
|
+
* reporting currency under a "$" and the receipt's USD currency: the service
|
|
130
|
+
* converts a foreign listing's figures and keeps the local ones as "...Original",
|
|
131
|
+
* but for an ADR it prints the local amounts as they are and names no currency
|
|
132
|
+
* (SK hynix's receipt shows the won revenue of its Korean listing as dollars).
|
|
133
|
+
*/
|
|
134
|
+
const STATEMENT_FIELDS = ["revenue", "netIncome", "freeCashFlow", "operatingCashFlow", "totalCash", "totalDebt", "dilutedEps"] as const;
|
|
135
|
+
|
|
136
|
+
/** A yearly figure this many times the company's whole market value is not dollars. */
|
|
137
|
+
const IMPLAUSIBLE_MULTIPLE_OF_MARKET_CAP = 10;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* A USD row with nothing converted whose revenue or net income dwarfs its own
|
|
141
|
+
* market cap has statements in another currency, and the row does not say
|
|
142
|
+
* which: the figures are left out rather than shown as wrong dollars. The
|
|
143
|
+
* ordinary range of revenue to market cap is a few times; the rows this
|
|
144
|
+
* catches sit at 15 to 4,000.
|
|
145
|
+
*/
|
|
146
|
+
function withoutUnconvertedStatements(company: BuildoutCompany): BuildoutCompany {
|
|
147
|
+
const converted = [company.revenueOriginal, company.netIncomeOriginal].some((value) => textOrNull(value) != null);
|
|
148
|
+
const usd = (company.currency?.trim().toUpperCase() || "USD") === "USD";
|
|
149
|
+
const cap = moneyAmount(company.marketCap);
|
|
150
|
+
if (converted || !usd || cap == null || cap <= 0) return company;
|
|
151
|
+
const revenue = moneyAmount(company.revenue);
|
|
152
|
+
const income = moneyAmount(company.netIncome);
|
|
153
|
+
const limit = cap * IMPLAUSIBLE_MULTIPLE_OF_MARKET_CAP;
|
|
154
|
+
if ((revenue == null || Math.abs(revenue) <= limit) && (income == null || Math.abs(income) <= limit)) return company;
|
|
155
|
+
return { ...company, ...Object.fromEntries(STATEMENT_FIELDS.map((field) => [field, null])) };
|
|
156
|
+
}
|
|
157
|
+
|
|
127
158
|
export function normalizeCompany(raw: RawObject): BuildoutCompany {
|
|
159
|
+
return withoutUnconvertedStatements(readCompany(raw));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function readCompany(raw: RawObject): BuildoutCompany {
|
|
128
163
|
const ticker = tickerSymbol(stringField(raw, "ticker"));
|
|
129
164
|
const name = stringField(raw, "name") ?? ticker ?? "Company";
|
|
130
165
|
const supplyChain = objectField(raw, "supplyChain", "supply_chain");
|
|
@@ -160,7 +160,10 @@ export function BuildoutPane({ focused, width, height }: PaneProps) {
|
|
|
160
160
|
const favoriteNotes = useRowNotes<BuildoutRow, string>(rows, { key: favoriteNoteKey });
|
|
161
161
|
const { setNote: setFavoriteNote, noteFor: favoriteNoteFor } = favoriteNotes;
|
|
162
162
|
clearFavoriteNotesRef.current = favoriteNotes.clearNotes;
|
|
163
|
-
const columns = useMemo(
|
|
163
|
+
const columns = useMemo(
|
|
164
|
+
() => columnsForTab(activeTab, selectedList, canFavorite, width),
|
|
165
|
+
[activeTab, canFavorite, selectedList, width],
|
|
166
|
+
);
|
|
164
167
|
const selectedRow = rows[selectedIndex] ?? rows[0] ?? null;
|
|
165
168
|
// The table's cells carry their own badges; this catalog only serves the open
|
|
166
169
|
// detail: its row's tickers, plus the loaded rows' tickers its text mentions.
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
BuildoutTabId,
|
|
9
9
|
SortComparable,
|
|
10
10
|
} from "./model/types";
|
|
11
|
+
import { dropColumnsToFit } from "../../../components/ui/table-layout";
|
|
11
12
|
import type { SortDirection } from "../../../utils/sort-values";
|
|
12
13
|
import { metricNumber, sourceDetailEntries, textOrNull, tickerSymbol } from "./format";
|
|
13
14
|
|
|
@@ -171,9 +172,29 @@ export function activeRows(
|
|
|
171
172
|
return state.intel.items.map((item) => ({ kind: "intel", item }));
|
|
172
173
|
}
|
|
173
174
|
|
|
174
|
-
|
|
175
|
+
/**
|
|
176
|
+
* What the Companies tab gives up when the pane is too narrow for its figures,
|
|
177
|
+
* least needed first: the descriptions, then the columns that only add to a
|
|
178
|
+
* figure already shown, instead of the right edge cutting Market Cap or a
|
|
179
|
+
* median. The star and the names stay at every width.
|
|
180
|
+
*/
|
|
181
|
+
const LIST_DROP_ORDER: readonly BuildoutColumnId[] = ["listDescription", "avgMargin", "avgReturn1y", "avgSectorGrowth"];
|
|
182
|
+
const COMPANY_DROP_ORDER: readonly BuildoutColumnId[] = [
|
|
183
|
+
"description", "employees", "dividendYield", "forwardPE", "netIncome", "margin", "sectorTech", "criticality",
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
/** The columns of a tab for a pane `width` cells wide; the table's scrollbar keeps its cell. */
|
|
187
|
+
export function columnsForTab(
|
|
188
|
+
activeTab: BuildoutTabId,
|
|
189
|
+
selectedList: BuildoutList | null,
|
|
190
|
+
canFavorite: boolean,
|
|
191
|
+
width: number,
|
|
192
|
+
): BuildoutColumn[] {
|
|
175
193
|
if (activeTab === "companies") {
|
|
176
|
-
|
|
194
|
+
const columns = selectedList
|
|
195
|
+
? canFavorite ? [favoriteColumn, ...companyColumns] : companyColumns
|
|
196
|
+
: listColumns;
|
|
197
|
+
return dropColumnsToFit(columns, width - 1, selectedList ? COMPANY_DROP_ORDER : LIST_DROP_ORDER);
|
|
177
198
|
}
|
|
178
199
|
if (activeTab === "sites") return canFavorite ? [favoriteColumn, ...siteColumns] : siteColumns;
|
|
179
200
|
return intelColumns;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
// Identity of the built-in composite plugins, shared by the native catalog and
|
|
2
2
|
// the browser catalog (catalog-browser.ts). Keep this file import-free so the
|
|
3
|
-
// browser catalog can read it without pulling in native-only modules
|
|
3
|
+
// browser catalog can read it without pulling in native-only modules; the
|
|
4
|
+
// one import, ATTN's listing flag, has none of its own.
|
|
4
5
|
//
|
|
5
6
|
// Screeners & Movers and Ownership & Insiders ship fewer panes on
|
|
6
7
|
// the web, so each catalog gives them their own description rather than
|
|
7
8
|
// advertising panes they lack.
|
|
8
9
|
|
|
10
|
+
import { ATTENTION_LISTED } from "./attention/listing";
|
|
11
|
+
|
|
9
12
|
export const applicationPluginMeta = {
|
|
10
13
|
id: "application",
|
|
11
14
|
name: "Application",
|
|
@@ -116,7 +119,8 @@ export const altDataPluginMeta = {
|
|
|
116
119
|
stateId: "ticker-research",
|
|
117
120
|
name: "Supply Chain & Alt Data",
|
|
118
121
|
version: "1.0.0",
|
|
119
|
-
|
|
122
|
+
// Research attention joins once ATTN is listed again (attention/listing.ts).
|
|
123
|
+
description: `Supply chains and exposure, government awards, hiring, apps and job postings, social mentions, ${ATTENTION_LISTED ? "research attention, " : ""}GPU prices, and power queues.`,
|
|
120
124
|
toggleable: true,
|
|
121
125
|
};
|
|
122
126
|
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "../../../components";
|
|
12
12
|
import { DetailReader } from "../../../components/reader/detail-reader";
|
|
13
13
|
import { loadingErrorFooterInfo, usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
14
|
+
import { createTtlCache } from "../../../data/ttl-cache";
|
|
14
15
|
import { fetchChangelogReleases, type ChangelogRelease } from "../../../updater/github-releases";
|
|
15
16
|
import { colors } from "../../../theme/colors";
|
|
16
17
|
import type { PaneProps } from "../../../types/plugin";
|
|
@@ -32,7 +33,8 @@ const CHANGELOG_CACHE_TTL_MS = 10 * 60 * 1000;
|
|
|
32
33
|
|
|
33
34
|
// Process-lifetime cache. Move it into the updater module if other
|
|
34
35
|
// surfaces start reading releases too.
|
|
35
|
-
|
|
36
|
+
const releaseCache = createTtlCache<ChangelogRelease[]>({ ttlMs: CHANGELOG_CACHE_TTL_MS });
|
|
37
|
+
const RELEASES = "releases";
|
|
36
38
|
|
|
37
39
|
type ChangelogColumn = DataTableColumn & { id: ChangelogColumnId };
|
|
38
40
|
type LoadStatus = "loading" | "loaded" | "error";
|
|
@@ -83,10 +85,10 @@ function ChangelogDetail({
|
|
|
83
85
|
function ChangelogPane({ focused, width, height }: PaneProps) {
|
|
84
86
|
const paneInstance = usePaneInstance();
|
|
85
87
|
const requestedVersion = paneInstance?.params?.version ?? null;
|
|
86
|
-
const [releases, setReleases] = useState<ChangelogRelease[]>(() =>
|
|
88
|
+
const [releases, setReleases] = useState<ChangelogRelease[]>(() => releaseCache.peek(RELEASES, { stale: true }) ?? []);
|
|
87
89
|
// The mount effect starts the request immediately, so the first paint is a
|
|
88
90
|
// spinner and never "No changelog entries found".
|
|
89
|
-
const [status, setStatus] = useState<LoadStatus>(() => (
|
|
91
|
+
const [status, setStatus] = useState<LoadStatus>(() => (releaseCache.peek(RELEASES, { stale: true }) ? "loaded" : "loading"));
|
|
90
92
|
const [error, setError] = useState<string | null>(null);
|
|
91
93
|
// The selected row and the open release are pane state, so a reload or a
|
|
92
94
|
// shared layout comes back to the same release.
|
|
@@ -98,8 +100,9 @@ function ChangelogPane({ focused, width, height }: PaneProps) {
|
|
|
98
100
|
const requestedVersionOpenedRef = useRef(false);
|
|
99
101
|
|
|
100
102
|
const loadReleases = useCallback(async (force = false) => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
const fresh = force ? undefined : releaseCache.peek(RELEASES);
|
|
104
|
+
if (fresh) {
|
|
105
|
+
setReleases(fresh);
|
|
103
106
|
setError(null);
|
|
104
107
|
setStatus("loaded");
|
|
105
108
|
return;
|
|
@@ -118,7 +121,7 @@ function ChangelogPane({ focused, width, height }: PaneProps) {
|
|
|
118
121
|
|
|
119
122
|
try {
|
|
120
123
|
const nextReleases = await fetchChangelogReleases(CHANGELOG_LIMIT, controller.signal);
|
|
121
|
-
|
|
124
|
+
releaseCache.set(RELEASES, nextReleases);
|
|
122
125
|
setReleases(nextReleases);
|
|
123
126
|
setStatus("loaded");
|
|
124
127
|
} catch (loadError) {
|
|
@@ -31,6 +31,8 @@ export interface CatalogSeriesRow {
|
|
|
31
31
|
/** The registered expression, `{ticker}` where a symbol goes. */
|
|
32
32
|
template: string;
|
|
33
33
|
url?: string;
|
|
34
|
+
/** A limit the publisher sets on the series' data, such as how far back its history goes. */
|
|
35
|
+
limitation?: string;
|
|
34
36
|
/** Asks for a symbol before charting. */
|
|
35
37
|
input?: SeriesInput;
|
|
36
38
|
/** The symbol a row filled in for a typed ticker charts. */
|
|
@@ -120,6 +122,7 @@ function rowFromEntry(entry: SeriesCatalogEntry): Omit<CatalogSeriesRow, "order"
|
|
|
120
122
|
expression,
|
|
121
123
|
template: entry.expression,
|
|
122
124
|
...(entry.url ? { url: entry.url } : {}),
|
|
125
|
+
...(entry.limitation ? { limitation: entry.limitation } : {}),
|
|
123
126
|
...(entry.input ? { input: entry.input } : {}),
|
|
124
127
|
...(entry.placement || entry.lines?.length ? {
|
|
125
128
|
seriesName: entry.name,
|
|
@@ -81,6 +81,10 @@ export function applyChartComposerCapabilityOptions(
|
|
|
81
81
|
next = { ...next, viewport: { ...next.viewport, extendedHours: true } };
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
if (capabilityId === "price-comparison" && options.totalReturn === true) {
|
|
85
|
+
next = { ...next, viewport: { ...next.viewport, totalReturn: true } };
|
|
86
|
+
}
|
|
87
|
+
|
|
84
88
|
if (PRICE_CAPABILITIES.has(capabilityId) && options.axisMode) {
|
|
85
89
|
const transform = options.axisMode === "percent" ? "percent" : "raw";
|
|
86
90
|
next = {
|
|
@@ -4,6 +4,7 @@ import { Box } from "../../../ui";
|
|
|
4
4
|
import {
|
|
5
5
|
DataTableView,
|
|
6
6
|
QueryBar,
|
|
7
|
+
usePaneNoticeFooter,
|
|
7
8
|
usePaneTabs,
|
|
8
9
|
useQueryBarSearch,
|
|
9
10
|
type DataTableCell,
|
|
@@ -242,6 +243,12 @@ export function DataCatalogPane({ focused, width, height }: PaneProps) {
|
|
|
242
243
|
}
|
|
243
244
|
}, []);
|
|
244
245
|
|
|
246
|
+
usePaneNoticeFooter({
|
|
247
|
+
registrationId: `${DATA_CATALOG_PANE_ID}:notices`,
|
|
248
|
+
focused,
|
|
249
|
+
notices: selectedRow?.limitation ? [`${selectedRow.label}: ${selectedRow.limitation}`] : [],
|
|
250
|
+
});
|
|
251
|
+
|
|
245
252
|
usePaneStatusLinkFooter({
|
|
246
253
|
registrationId: DATA_CATALOG_PANE_ID,
|
|
247
254
|
focused,
|
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
SeriesTimestampMode,
|
|
11
11
|
SeriesTransform,
|
|
12
12
|
} from "../../../../time-series/types";
|
|
13
|
-
import { hasStudyInputCount, validateChartSpec } from "../../../../time-series/spec";
|
|
13
|
+
import { hasStudyInputCount, isOhlcSeriesStyle, validateChartSpec } from "../../../../time-series/spec";
|
|
14
14
|
import { isPlainKey } from "../../../../utils/keyboard";
|
|
15
15
|
import { getSharedRegistry } from "../../../registry";
|
|
16
16
|
import {
|
|
@@ -415,7 +415,7 @@ export function useSeriesEditorController({
|
|
|
415
415
|
: current.panels,
|
|
416
416
|
series: replaceAt(current.series, selectedIndex, (() => {
|
|
417
417
|
const currentSeries = current.series[selectedIndex]!;
|
|
418
|
-
const ohlcStyle = currentSeries.style
|
|
418
|
+
const ohlcStyle = isOhlcSeriesStyle(currentSeries.style);
|
|
419
419
|
return {
|
|
420
420
|
...currentSeries,
|
|
421
421
|
style: transform !== "raw" && ohlcStyle
|
|
@@ -138,6 +138,12 @@ export const paneSchemas = {
|
|
|
138
138
|
],
|
|
139
139
|
defaultValue: "percent",
|
|
140
140
|
},
|
|
141
|
+
{
|
|
142
|
+
key: "totalReturn",
|
|
143
|
+
description: "Reinvest each cash distribution at its ex-date close, so the returns are total returns, not price returns.",
|
|
144
|
+
type: "boolean",
|
|
145
|
+
defaultValue: false,
|
|
146
|
+
},
|
|
141
147
|
{
|
|
142
148
|
key: "chartResolution",
|
|
143
149
|
description: BAR_SIZE_DESCRIPTION,
|
|
@@ -151,7 +157,7 @@ export const paneSchemas = {
|
|
|
151
157
|
discovery: {
|
|
152
158
|
id: "price-comparison",
|
|
153
159
|
aliases: ["stock comparison", "price performance comparison", "returns comparison", "compare stock prices"],
|
|
154
|
-
limitations: ["Compares closing-price changes in each listing's currency over shared observation dates. Cash distributions and FX conversion are excluded;
|
|
160
|
+
limitations: ["Compares closing-price changes in each listing's currency over shared observation dates. Cash distributions and FX conversion are excluded unless totalReturn is set, which reinvests each distribution at its ex-date close; FX conversion is never applied."],
|
|
155
161
|
screenshotReadiness: "ready",
|
|
156
162
|
},
|
|
157
163
|
},
|
|
@@ -228,6 +228,8 @@ export async function loadChartPaneModel(
|
|
|
228
228
|
...reported,
|
|
229
229
|
...name ? { name } : {},
|
|
230
230
|
...valueDecimals !== undefined ? { textDecimals: valueDecimals } : {},
|
|
231
|
+
// A company's reported total (revenue, market cap) reads as 63.33B in the report, as in the legend and the tables.
|
|
232
|
+
...series.unitGroup.split(":")[0] === "currency-total" ? { textCompact: true } : {},
|
|
231
233
|
derived: !ids.has(series.id),
|
|
232
234
|
points: series.points.map((point) => ({
|
|
233
235
|
...point,
|
|
@@ -251,7 +253,7 @@ export async function loadChartPaneModel(
|
|
|
251
253
|
...(chart.priceHistoryIntegrity?.length ? { priceHistoryIntegrity: chart.priceHistoryIntegrity } : {}),
|
|
252
254
|
...(valuationPriceIssues.length ? { valuationPriceIssues } : {}),
|
|
253
255
|
...(periodCoverage.length ? { periodCoverage } : {}),
|
|
254
|
-
notices: [...chart.warnings.filter((warning) => warning === FINANCIAL_VINTAGE_NOTICE || warning === SEC_EPS_BASIS_NOTICE || warning === chart.priceComparison?.notice
|
|
256
|
+
notices: [...chart.coverageNotices ?? [], ...chart.returnBasisNotice ? [chart.returnBasisNotice] : [], ...chart.warnings.filter((warning) => warning === FINANCIAL_VINTAGE_NOTICE || warning === SEC_EPS_BASIS_NOTICE || warning === chart.priceComparison?.notice
|
|
255
257
|
|| chart.delistings?.some((delisting) => delisting.notice === warning)), ...integrityNotices],
|
|
256
258
|
priceComparison: chart.priceComparison ?? null,
|
|
257
259
|
summaries: chart.series.filter((series) => !series.profile).map((series) => ({ id: series.id, ...summarizeResolvedSeries(series) })),
|
|
@@ -723,6 +725,9 @@ export function chartHeadless(template: keyof typeof paneSchemas): HeadlessPaneD
|
|
|
723
725
|
}, context.marketData) };
|
|
724
726
|
}
|
|
725
727
|
const model = await loadChartPaneModel(spec, context);
|
|
728
|
+
// A bare coin ticker charted as another listing (ETH, the Grayscale fund) says where the coin is, as `quote` does.
|
|
729
|
+
const coinNotes = (await Promise.all(listings.values())).flatMap((read) => read?.coinNote ?? []);
|
|
730
|
+
if (coinNotes.length) model.warnings = [...model.warnings ?? [], ...coinNotes];
|
|
726
731
|
if (template === "graph-intraday-price-pane" && !intradayHistories.length && model.chart.viewport) {
|
|
727
732
|
const { start, end } = model.chart.viewport;
|
|
728
733
|
for (const { target, data } of [
|
|
@@ -453,7 +453,7 @@ const chartComposerTemplates: PaneTemplateDef[] = [
|
|
|
453
453
|
prefix: "CMP",
|
|
454
454
|
aliases: ["COMP", "TRA"],
|
|
455
455
|
label: "Comparison Chart",
|
|
456
|
-
description: "Compare price returns over shared observation dates for two or more tickers. A leveraged or inverse fund gets its daily objective drawn from its index fund (3× daily QQQ beside TQQQ) and its decay against it.",
|
|
456
|
+
description: "Compare price returns over shared observation dates for two or more tickers, or total returns with distributions reinvested. A leveraged or inverse fund gets its daily objective drawn from its index fund (3× daily QQQ beside TQQQ) and its decay against it.",
|
|
457
457
|
// Leveraged and inverse funds: the daily line and the decay come with the comparison.
|
|
458
458
|
keywords: ["leveraged", "inverse", "decay", "daily-reset", "daily reset", "volatility decay"],
|
|
459
459
|
argKind: "ticker-list",
|