gloomberb 0.18.4 → 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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +169 -5
- package/src/account/auth-model.ts +7 -3
- package/src/api-client/auth.ts +9 -4
- package/src/api-client/catalysts.ts +2 -1
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +31 -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 +171 -11
- package/src/api-client/live-scan.ts +91 -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 +13 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/request.ts +222 -41
- package/src/api-client/research-activity.ts +6 -12
- package/src/api-client/types.ts +74 -0
- package/src/app/keybindings/index.ts +3 -0
- package/src/app/keybindings/labels.ts +18 -1
- package/src/app/keybindings/resolve.ts +14 -0
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +42 -27
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +1 -1
- 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/fred-series.ts +5 -0
- package/src/cli/commands/keybindings.ts +23 -0
- package/src/cli/commands/market.ts +138 -46
- package/src/cli/commands/options-chain.ts +107 -10
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +176 -39
- package/src/cli/commands/search.ts +39 -3
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +57 -35
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +10 -2
- package/src/cli/export-source.ts +29 -1
- package/src/cli/glossary.ts +73 -1
- package/src/cli/history-rows.ts +87 -16
- package/src/cli/index.ts +2 -0
- package/src/cli/known-gaps.ts +5 -2
- package/src/cli/listing-arg.ts +195 -56
- 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 +111 -16
- package/src/cli/pane-functions/function-help.ts +23 -13
- package/src/cli/pane-functions/headless.ts +56 -12
- package/src/cli/pane-functions/index.ts +58 -16
- package/src/cli/pane-functions/market-session.ts +81 -9
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/report.ts +5 -1
- package/src/cli/pane-functions/resolver.ts +35 -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 +30 -13
- package/src/cli/report-tables.ts +18 -7
- package/src/cli/restore-plugins.ts +13 -1
- package/src/cli/result.ts +57 -5
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +26 -4
- package/src/components/chart/composite/format.ts +27 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/levels.ts +2 -0
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +61 -5
- package/src/components/chart/composite/range-statistics.ts +62 -0
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +46 -7
- package/src/components/chart/composite/series-axis-tags.tsx +16 -9
- 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/types.ts +4 -0
- 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/panel/prompt-binding.ts +0 -8
- package/src/components/command-bar/workflow/alert.ts +1 -1
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/header.tsx +26 -40
- package/src/components/layout/pane/footer/index.tsx +1 -0
- package/src/components/layout/pane/footer/model.ts +27 -0
- package/src/components/onboarding/onboarding-frame.tsx +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
- 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/display.tsx +5 -1
- 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/core/state/app/layout.ts +2 -0
- package/src/core/state/app/state.ts +9 -0
- package/src/core/state/app/types.ts +3 -0
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/saved-copies.ts +55 -16
- 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 +427 -216
- package/src/i18n/es.ts +19 -3
- package/src/i18n/ja.ts +19 -3
- package/src/i18n/ko.ts +19 -3
- package/src/i18n/zh-cn.ts +19 -3
- package/src/i18n/zh-tw.ts +19 -3
- package/src/market-data/coordinator/financials.ts +10 -2
- package/src/market-data/coordinator/index.ts +3 -1
- package/src/market-data/coordinator/quotes.ts +4 -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 +48 -7
- package/src/market-data/market/dated-previous-close.ts +203 -0
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/market/futures-week.ts +68 -0
- package/src/market-data/market/status.ts +16 -4
- package/src/market-data/options-alternatives.ts +141 -4
- package/src/market-data/quotes/contributions.ts +20 -2
- package/src/market-data/quotes/resolution.ts +3 -0
- package/src/market-data/selectors.ts +3 -0
- 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/notifications/notification-log.ts +6 -4
- package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +380 -82
- package/src/plugins/builtin/alerts/command.ts +31 -0
- package/src/plugins/builtin/alerts/constants.ts +1 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- package/src/plugins/builtin/alerts/fired-log.ts +113 -8
- package/src/plugins/builtin/alerts/format.ts +80 -4
- package/src/plugins/builtin/alerts/headless.ts +30 -7
- package/src/plugins/builtin/alerts/history-rows.ts +7 -3
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +135 -22
- package/src/plugins/builtin/alerts/live.ts +3 -3
- package/src/plugins/builtin/alerts/metric-command.ts +134 -0
- package/src/plugins/builtin/alerts/metric-data.ts +111 -0
- package/src/plugins/builtin/alerts/metric-model.ts +324 -0
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
- package/src/plugins/builtin/alerts/pane.tsx +90 -39
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +37 -10
- package/src/plugins/builtin/alerts/types.ts +7 -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/backtest/headless.ts +10 -5
- package/src/plugins/builtin/backtest/pane.tsx +9 -4
- package/src/plugins/builtin/backtest/presets.ts +29 -3
- package/src/plugins/builtin/broker-manager/index.tsx +10 -5
- package/src/plugins/builtin/broker-manager/model.ts +6 -1
- package/src/plugins/builtin/broker-trading/controller.ts +22 -12
- package/src/plugins/builtin/broker-trading/index.tsx +13 -1
- package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
- package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
- 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 +7 -3
- package/src/plugins/builtin/catalysts/model.ts +2 -1
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
- package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
- package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
- package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
- package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
- package/src/plugins/builtin/chart-composer/formula.ts +71 -18
- package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
- package/src/plugins/builtin/chart-composer/headless.ts +19 -3
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
- package/src/plugins/builtin/chart-composer/presets.ts +50 -15
- package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
- package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
- package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
- package/src/plugins/builtin/chat/content/composer.tsx +13 -6
- package/src/plugins/builtin/chat/content/footer.tsx +6 -3
- package/src/plugins/builtin/chat/content/index.tsx +50 -21
- package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
- package/src/plugins/builtin/chat/index.tsx +23 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
- package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
- package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
- package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -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/cloud/plugin.tsx +1 -1
- 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/calendar-model.ts +14 -9
- package/src/plugins/builtin/econ/figures.ts +72 -0
- package/src/plugins/builtin/econ/index.tsx +33 -3
- package/src/plugins/builtin/econ/types.ts +4 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
- package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
- package/src/plugins/builtin/econ-statistics/view.ts +3 -0
- 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 +70 -15
- package/src/plugins/builtin/exposure/charts.tsx +2 -2
- package/src/plugins/builtin/exposure/model.ts +13 -1
- package/src/plugins/builtin/exposure/pane.tsx +4 -4
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/futures/model.ts +2 -1
- package/src/plugins/builtin/futures/table.tsx +2 -2
- 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 +11 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
- package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
- package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -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/keybindings-editor.tsx +18 -14
- 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 +36 -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/pane.tsx +7 -5
- 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/footer.ts +2 -2
- 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 +82 -0
- package/src/plugins/builtin/members/client.ts +19 -4
- package/src/plugins/builtin/members/headless.ts +20 -7
- package/src/plugins/builtin/members/index.tsx +9 -9
- package/src/plugins/builtin/members/model.ts +23 -4
- package/src/plugins/builtin/members/pane.tsx +25 -15
- 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 +54 -6
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/model-spot.ts +5 -2
- package/src/plugins/builtin/options/strike-window.ts +2 -1
- package/src/plugins/builtin/options/table.ts +6 -9
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +42 -12
- package/src/plugins/builtin/options-positioning/client.ts +30 -0
- package/src/plugins/builtin/options-positioning/headless.ts +74 -21
- package/src/plugins/builtin/options-positioning/index.tsx +4 -4
- package/src/plugins/builtin/options-positioning/model.ts +122 -14
- package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
- 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/freshness.ts +12 -10
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/live-rows.ts +10 -3
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +32 -31
- 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 +12 -7
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- 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/summary/index.tsx +11 -8
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/rate-path/pane.tsx +2 -2
- 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 +11 -9
- 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/feed.ts +2 -2
- 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 +40 -6
- package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
- package/src/plugins/builtin/scanner/live-feed.ts +168 -0
- package/src/plugins/builtin/scanner/live-headless.ts +138 -0
- package/src/plugins/builtin/scanner/live-model.ts +362 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
- package/src/plugins/builtin/scanner/live-track.ts +186 -0
- 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 +3 -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/macro-releases.ts +65 -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/scanner-capture.ts +3 -0
- 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 +10 -3
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/headless.ts +6 -4
- package/src/plugins/builtin/themes/model.ts +17 -8
- package/src/plugins/builtin/themes/pane.tsx +7 -7
- 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-footer.ts +6 -1
- 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/charts.tsx +9 -4
- package/src/plugins/builtin/vol-surface/client.ts +9 -17
- package/src/plugins/builtin/vol-surface/headless.ts +63 -14
- package/src/plugins/builtin/vol-surface/index.tsx +2 -2
- package/src/plugins/builtin/vol-surface/model.ts +22 -7
- package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
- package/src/plugins/builtin/vol-surface/tables.ts +31 -3
- package/src/plugins/builtin/volatility/dvol.ts +46 -0
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +4 -4
- package/src/plugins/builtin/volatility/series.ts +26 -0
- package/src/plugins/builtin/world-indices/footer.ts +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-browser.ts +13 -8
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +5 -5
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
- package/src/remote/resources.ts +2 -1
- package/src/renderers/browser/app-services.ts +1 -7
- package/src/renderers/browser/config-host.ts +2 -55
- package/src/renderers/browser/main.tsx +7 -57
- 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 +222 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
- 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/relaunch.ts +74 -25
- package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
- package/src/renderers/electrobun/bun/index.ts +25 -1
- package/src/renderers/electrobun/shared/app-name.ts +30 -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/cli-pane-shot-frame.tsx +3 -3
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -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/renderers/opentui/toast-host.tsx +71 -43
- 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/gloomberb-cloud/normalizers.ts +2 -0
- package/src/sources/provider-router/batches.ts +10 -2
- package/src/sources/provider-router/financial-routes.ts +3 -3
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/history.ts +28 -7
- package/src/sources/provider-router/index.ts +149 -10
- package/src/sources/provider-router/search.ts +13 -2
- package/src/state/app/context/index.tsx +3 -0
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/auth-transition.ts +19 -9
- package/src/sync/baseline.ts +10 -4
- package/src/sync/controller.ts +70 -19
- package/src/sync/core-contributors.ts +90 -31
- package/src/sync/react.ts +4 -1
- package/src/sync/types.ts +3 -0
- 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/telemetry/usage-counts.ts +0 -18
- package/src/test-support/preload.ts +14 -0
- package/src/theme/colors.ts +8 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name.ts +103 -3
- package/src/tickers/listing-venues.ts +38 -3
- package/src/tickers/search/bare-symbol.ts +36 -0
- package/src/tickers/search/index.ts +36 -7
- package/src/tickers/search/ranking.ts +10 -1
- package/src/tickers/search/types.ts +5 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/base-series.ts +13 -5
- package/src/time-series/daily-valuation.ts +62 -0
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +2 -4
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/forward-valuation.ts +32 -0
- package/src/time-series/fundamentals.ts +198 -7
- package/src/time-series/hooks.ts +1 -0
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/price-units.ts +72 -2
- package/src/time-series/resolve-context.ts +7 -1
- package/src/time-series/resolve.ts +116 -15
- package/src/time-series/series-presentation.ts +40 -6
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +5 -3
- package/src/time-series/studies.ts +278 -74
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +3 -3
- package/src/time-series/types.ts +27 -1
- package/src/time-series/valuation-releases.ts +39 -0
- package/src/types/data-provider.ts +9 -1
- package/src/types/desktop-window.ts +2 -0
- package/src/types/financials.ts +73 -2
- package/src/types/headless.ts +61 -5
- package/src/types/instrument.ts +3 -0
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +10 -1
- package/src/ui/host.tsx +3 -0
- package/src/updater/command-names.ts +12 -7
- 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/financial-statements.ts +6 -1
- 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/network-failure.ts +9 -6
- package/src/utils/option-expiry.ts +48 -1
- package/src/utils/options.ts +10 -0
- package/src/utils/plugin-proxy-hosts.json +1 -0
- package/src/utils/price-history.ts +12 -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
- package/src/renderers/browser/first-screen.ts +0 -193
|
@@ -50,6 +50,7 @@ export function SeriesEditorDialog({ dialogId, resolve, initialSpec }: SeriesEdi
|
|
|
50
50
|
quickAddItems,
|
|
51
51
|
quickAddLoading,
|
|
52
52
|
quickAddError,
|
|
53
|
+
retryQuickAdd,
|
|
53
54
|
quickAddQuery,
|
|
54
55
|
quickAddRef,
|
|
55
56
|
quickAddSelection,
|
|
@@ -123,7 +124,7 @@ export function SeriesEditorDialog({ dialogId, resolve, initialSpec }: SeriesEdi
|
|
|
123
124
|
onBlur={deactivateQuickAdd}
|
|
124
125
|
/>
|
|
125
126
|
|
|
126
|
-
{quickAddActive && quickAddQuery.trim() && (
|
|
127
|
+
{(quickAddActive || quickAddError) && quickAddQuery.trim() && (
|
|
127
128
|
quickAddItems.length > 0 ? (
|
|
128
129
|
<Box width={contentWidth} overflow="hidden">
|
|
129
130
|
<ListView
|
|
@@ -138,13 +139,14 @@ export function SeriesEditorDialog({ dialogId, resolve, initialSpec }: SeriesEdi
|
|
|
138
139
|
onActivate={(_, index) => { beginQuickAdd(); addCatalogSuggestion(quickAddSuggestions[index]); }}
|
|
139
140
|
/>
|
|
140
141
|
</Box>
|
|
141
|
-
) : !
|
|
142
|
-
<EmptyState title={quickAddError} status="error" />
|
|
143
|
-
) : (
|
|
142
|
+
) : !quickAddError ? (
|
|
144
143
|
<Text fg={colors.textMuted}>
|
|
145
144
|
{quickAddLoading ? "Searching instruments…" : "No matching security or metric."}
|
|
146
145
|
</Text>
|
|
147
|
-
)
|
|
146
|
+
) : null
|
|
147
|
+
)}
|
|
148
|
+
{quickAddQuery.trim() && quickAddError && (
|
|
149
|
+
<EmptyState title={quickAddError} status="error" retry={{ onRetry: () => { beginQuickAdd(); return retryQuickAdd(); } }} />
|
|
148
150
|
)}
|
|
149
151
|
|
|
150
152
|
{items.length > 0 ? (
|
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
import { currencyPairSpelling } from "../../../utils/currency-pair";
|
|
2
2
|
import { dashedCoinPair } from "../../../utils/crypto-pair";
|
|
3
|
-
import { GALLONS_PER_BARREL } from "../../../time-series/price-units";
|
|
3
|
+
import { GALLONS_PER_BARREL, isBarrelsPerTonne } from "../../../time-series/price-units";
|
|
4
4
|
import { MAX_CHART_COMPOSER_SERIES } from "./chart-spec";
|
|
5
5
|
import { parseSeriesExpression, resolveChartFieldAlias, type ParsedSeriesExpression } from "./series-expression";
|
|
6
6
|
import { getTimeSeriesField } from "../../../time-series/field-catalog";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Series formulas: a sum of series each times a number, the whole optionally
|
|
10
|
-
* in brackets over
|
|
10
|
+
* in brackets over or times numbers and series.
|
|
11
11
|
*
|
|
12
12
|
* 42*HO=F - CL=F heating-oil crack, USD per barrel
|
|
13
13
|
* HO=F*42 - BZ=F a multiplier on either side
|
|
14
14
|
* (2*42*RB=F + 42*HO=F - 3*CL=F)/3 3:2:1 crack, per barrel of crude
|
|
15
|
+
* (42*HO=F - BZ=F)/EURUSD=X the crack in EUR per barrel
|
|
16
|
+
* 7.45*42*HO=F - 7.45*BZ=F a gasoil-style crack per tonne
|
|
15
17
|
*
|
|
16
18
|
* A literal 42 on a series is the 42 gallons of a barrel: it makes a price per
|
|
17
|
-
* gallon a price per barrel.
|
|
19
|
+
* gallon a price per barrel. A literal 7.45 or 8.45 is the barrels in a tonne
|
|
20
|
+
* of gasoil or gasoline: it makes a price per barrel a price per tonne. Any
|
|
21
|
+
* other number is a count and keeps the unit. A series the whole formula is
|
|
22
|
+
* divided or multiplied by changes its unit as the arithmetic does: over
|
|
23
|
+
* EURUSD=X (USD per EUR), dollars are euros.
|
|
18
24
|
*
|
|
19
25
|
* Grammar, no other precedence to learn:
|
|
20
|
-
* formula := sum | "(" sum ")"
|
|
26
|
+
* formula := sum | "(" sum ")" { ( "/" | "*" ) factor }
|
|
21
27
|
* sum := [ "+" | "-" ] term { ( "+" | "-" ) term }
|
|
22
28
|
* term := factor { ( "*" | "×" ) factor } with exactly one series
|
|
23
29
|
* factor := number | series
|
|
@@ -29,17 +35,30 @@ interface FormulaLeg {
|
|
|
29
35
|
weight: number;
|
|
30
36
|
/** The leg was multiplied by a literal 42: the gallons in a barrel. */
|
|
31
37
|
perBarrel: boolean;
|
|
38
|
+
/** The barrels in a tonne the leg was multiplied by (7.45 or 8.45), or null. */
|
|
39
|
+
perTonne: number | null;
|
|
32
40
|
/** The leg as written: `HO=F`. */
|
|
33
41
|
text: string;
|
|
34
42
|
}
|
|
35
43
|
|
|
44
|
+
/** A series the whole formula is multiplied (power 1) or divided (-1) by. */
|
|
45
|
+
interface FormulaFactor {
|
|
46
|
+
series: ParsedSeriesExpression;
|
|
47
|
+
power: 1 | -1;
|
|
48
|
+
/** The series as written: `EURUSD=X`. */
|
|
49
|
+
text: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
36
52
|
export interface ParsedFormula {
|
|
37
53
|
legs: FormulaLeg[];
|
|
38
54
|
/** What the whole sum is divided by; 1 when it is not. */
|
|
39
55
|
divisor: number;
|
|
56
|
+
/** The series the whole sum is then multiplied or divided by, in the order written. */
|
|
57
|
+
factors: FormulaFactor[];
|
|
40
58
|
}
|
|
41
59
|
|
|
42
60
|
const EXAMPLE = "(2*42*RB=F + 42*HO=F - 3*CL=F)/3";
|
|
61
|
+
const SCALED_EXAMPLE = "(42*HO=F - BZ=F)/EURUSD=X";
|
|
43
62
|
|
|
44
63
|
type Token =
|
|
45
64
|
| { kind: "number"; value: number; text: string }
|
|
@@ -75,9 +94,20 @@ function minusIsOperator(atom: string, previous: Token | undefined, rest: string
|
|
|
75
94
|
)));
|
|
76
95
|
}
|
|
77
96
|
|
|
97
|
+
/**
|
|
98
|
+
* After the formula's closing bracket every `/` divides, except inside a
|
|
99
|
+
* currency or coin pair written with one: `(...)/EUR/USD` is over EUR/USD.
|
|
100
|
+
*/
|
|
101
|
+
function slashIsPairSpelling(atom: string, rest: string): boolean {
|
|
102
|
+
const quote = rest.match(/^[^\s*×+()/]*/)![0];
|
|
103
|
+
const symbol = `${atom}/${quote.split(":")[0]}`;
|
|
104
|
+
return /^[A-Za-z]{3,}$/.test(atom) && !!quote && (!!currencyPairSpelling(symbol) || !!dashedCoinPair(symbol));
|
|
105
|
+
}
|
|
106
|
+
|
|
78
107
|
function tokenize(expression: string): Token[] {
|
|
79
108
|
const tokens: Token[] = [];
|
|
80
109
|
let atom = "";
|
|
110
|
+
let closed = false;
|
|
81
111
|
const flush = () => {
|
|
82
112
|
if (!atom) return;
|
|
83
113
|
tokens.push(NUMBER.test(atom) ? { kind: "number", value: Number(atom), text: atom } : { kind: "atom", text: atom });
|
|
@@ -89,11 +119,14 @@ function tokenize(expression: string): Token[] {
|
|
|
89
119
|
flush();
|
|
90
120
|
} else if (char === "*" || char === "×" || char === "+" || char === "(" || char === ")") {
|
|
91
121
|
flush();
|
|
122
|
+
if (char === ")") closed = true;
|
|
92
123
|
tokens.push({ kind: char === "×" ? "*" : char, text: char });
|
|
93
124
|
} else if (char === "-" && (minusIsOperator(atom, tokens.at(-1), expression.slice(index + 1)) || /\s/.test(expression[index - 1] ?? " "))) {
|
|
94
125
|
flush();
|
|
95
126
|
tokens.push({ kind: "-", text: char });
|
|
96
|
-
} else if (char === "/" && !atom &&
|
|
127
|
+
} else if (char === "/" && (!atom || (closed && !slashIsPairSpelling(atom, expression.slice(index + 1))))) {
|
|
128
|
+
// A slash on its own divides; inside a word it is part of a pair (EUR/USD), until the brackets close.
|
|
129
|
+
flush();
|
|
97
130
|
tokens.push({ kind: "/", text: char });
|
|
98
131
|
} else {
|
|
99
132
|
atom += char;
|
|
@@ -111,7 +144,7 @@ function fail(message: string): never {
|
|
|
111
144
|
function legSeries(text: string): ParsedSeriesExpression {
|
|
112
145
|
const symbol = text.split(":")[0]!;
|
|
113
146
|
if (symbol.includes("/") && !currencyPairSpelling(symbol) && !dashedCoinPair(symbol)) {
|
|
114
|
-
fail(`"${text}" divides inside the formula. Divide the whole formula by a number
|
|
147
|
+
fail(`"${text}" divides inside the formula. Divide the whole formula in brackets by a number or a series, as in ${SCALED_EXAMPLE}; chart a ratio of two series as A / B on its own.`);
|
|
115
148
|
}
|
|
116
149
|
return parseSeriesExpression(text)
|
|
117
150
|
?? fail(`"${text}" is not a series. Use SYMBOL, SYMBOL:field, FUT:code, UST:maturity, FRED:series, GEO:series or CAP:capability-id:series-id.`);
|
|
@@ -143,7 +176,7 @@ export function parseFormulaExpression(expression: string): ParsedFormula | null
|
|
|
143
176
|
const closes = tokens.filter((token) => token.kind === ")").length;
|
|
144
177
|
if (opens !== closes) fail(`The brackets in "${trimmed}" do not pair up.`);
|
|
145
178
|
if (opens > 1 || (opens === 1 && !bracketed)) {
|
|
146
|
-
fail(`Brackets go around the whole formula only,
|
|
179
|
+
fail(`Brackets go around the whole formula only, then divide or multiply it: ${EXAMPLE}, ${SCALED_EXAMPLE}.`);
|
|
147
180
|
}
|
|
148
181
|
|
|
149
182
|
const legs: FormulaLeg[] = [];
|
|
@@ -158,6 +191,7 @@ export function parseFormulaExpression(expression: string): ParsedFormula | null
|
|
|
158
191
|
// One term: numbers and exactly one series, multiplied.
|
|
159
192
|
let weight = sign;
|
|
160
193
|
let perBarrel = false;
|
|
194
|
+
let perTonne: number | null = null;
|
|
161
195
|
const factors: Token[] = [];
|
|
162
196
|
for (;;) {
|
|
163
197
|
const factor = take();
|
|
@@ -171,6 +205,7 @@ export function parseFormulaExpression(expression: string): ParsedFormula | null
|
|
|
171
205
|
if (factor.kind === "number") {
|
|
172
206
|
if (factor.value === 0) fail("A multiplier of 0 removes its series; leave the term out instead.");
|
|
173
207
|
if (factor.value === GALLONS_PER_BARREL && !perBarrel) perBarrel = true;
|
|
208
|
+
else if (isBarrelsPerTonne(factor.value) && perTonne === null) perTonne = factor.value;
|
|
174
209
|
weight *= factor.value;
|
|
175
210
|
}
|
|
176
211
|
if (peek()?.kind !== "*") break;
|
|
@@ -189,14 +224,14 @@ export function parseFormulaExpression(expression: string): ParsedFormula | null
|
|
|
189
224
|
if (legs.some((leg) => leg.text.toUpperCase() === text.toUpperCase())) {
|
|
190
225
|
fail(`${text} appears twice. Write it once with its combined multiplier, such as 2*${text}.`);
|
|
191
226
|
}
|
|
192
|
-
legs.push({ series: parsed, weight, perBarrel, text });
|
|
227
|
+
legs.push({ series: parsed, weight, perBarrel, perTonne, text });
|
|
193
228
|
expectTerm = false;
|
|
194
229
|
|
|
195
230
|
const next = peek();
|
|
196
231
|
if (!next || next.kind === ")") break;
|
|
197
232
|
if (next.kind !== "+" && next.kind !== "-") {
|
|
198
233
|
fail(next.kind === "/"
|
|
199
|
-
? `Divide the whole formula in brackets by a number, as in ${
|
|
234
|
+
? `Divide the whole formula in brackets by a number or a series, as in ${SCALED_EXAMPLE}.`
|
|
200
235
|
: `Expected + or - after ${written}, not "${next.text}".`);
|
|
201
236
|
}
|
|
202
237
|
take();
|
|
@@ -208,26 +243,44 @@ export function parseFormulaExpression(expression: string): ParsedFormula | null
|
|
|
208
243
|
}
|
|
209
244
|
|
|
210
245
|
let divisor = 1;
|
|
246
|
+
const scaleFactors: FormulaFactor[] = [];
|
|
211
247
|
if (bracketed) {
|
|
212
248
|
if (take()?.kind !== ")") fail(`The brackets in "${trimmed}" do not pair up.`);
|
|
213
|
-
|
|
214
|
-
take();
|
|
249
|
+
while (peek()?.kind === "/" || peek()?.kind === "*") {
|
|
250
|
+
const divides = take()!.kind === "/";
|
|
215
251
|
const value = take();
|
|
216
|
-
if (!value || value.kind !== "number") {
|
|
217
|
-
fail(
|
|
252
|
+
if (!value || (value.kind !== "number" && value.kind !== "atom")) {
|
|
253
|
+
fail(`${divides ? "Divide" : "Multiply"} the whole formula by a number or a series, such as ${divides ? "/3 or /EURUSD=X" : "*7.45 or *EURUSD=X"}${value ? `, not ${value.text}` : ""}.`);
|
|
254
|
+
}
|
|
255
|
+
if (value.kind === "number") {
|
|
256
|
+
if (value.value === 0) fail(divides ? "A formula cannot be divided by 0." : "A multiplier of 0 removes the formula; leave it out instead.");
|
|
257
|
+
if (divides) {
|
|
258
|
+
divisor *= value.value;
|
|
259
|
+
} else {
|
|
260
|
+
// A number times the whole sum multiplies each leg, converting its unit as it would there.
|
|
261
|
+
for (const leg of legs) {
|
|
262
|
+
if (value.value === GALLONS_PER_BARREL && !leg.perBarrel) leg.perBarrel = true;
|
|
263
|
+
else if (isBarrelsPerTonne(value.value) && leg.perTonne === null) leg.perTonne = value.value;
|
|
264
|
+
leg.weight *= value.value;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const text = value.text;
|
|
270
|
+
if ([...legs, ...scaleFactors].some((entry) => entry.text.toUpperCase() === text.toUpperCase())) {
|
|
271
|
+
fail(`${text} appears twice in the formula. Use each series once.`);
|
|
218
272
|
}
|
|
219
|
-
|
|
220
|
-
divisor = value.value;
|
|
273
|
+
scaleFactors.push({ series: legSeries(text), power: divides ? -1 : 1, text });
|
|
221
274
|
}
|
|
222
275
|
}
|
|
223
276
|
const rest = tokens.slice(position);
|
|
224
277
|
if (rest.length > 0) {
|
|
225
278
|
fail(rest[0]!.kind === "/" || rest[0]!.kind === "*"
|
|
226
|
-
? `Divide the whole formula in brackets
|
|
279
|
+
? `Divide or multiply the whole formula in brackets, as in ${EXAMPLE} or ${SCALED_EXAMPLE}.`
|
|
227
280
|
: `Unexpected "${rest.map((token) => token.text).join(" ")}" after the formula.`);
|
|
228
281
|
}
|
|
229
|
-
if (legs.length > MAX_CHART_COMPOSER_SERIES) {
|
|
282
|
+
if (legs.length + scaleFactors.length > MAX_CHART_COMPOSER_SERIES) {
|
|
230
283
|
fail(`A formula takes up to ${MAX_CHART_COMPOSER_SERIES} series.`);
|
|
231
284
|
}
|
|
232
|
-
return { legs, divisor };
|
|
285
|
+
return { legs, divisor, factors: scaleFactors };
|
|
233
286
|
}
|
|
@@ -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
|
},
|
|
@@ -203,6 +209,13 @@ export const paneSchemas = {
|
|
|
203
209
|
"valuation-graph-pane": {
|
|
204
210
|
argument: { kind: "tickers" },
|
|
205
211
|
options: [
|
|
212
|
+
{
|
|
213
|
+
key: "rangePreset",
|
|
214
|
+
description: "Chart history window.",
|
|
215
|
+
type: "enum",
|
|
216
|
+
aliases: ["range"],
|
|
217
|
+
values: HISTORY_RANGE_VALUES,
|
|
218
|
+
},
|
|
206
219
|
{
|
|
207
220
|
key: "metric",
|
|
208
221
|
description: "Valuation metric to compare.",
|
|
@@ -219,9 +232,13 @@ export const paneSchemas = {
|
|
|
219
232
|
// The same metric the GE template opens with in the app.
|
|
220
233
|
defaultValue: "trailingPE",
|
|
221
234
|
},
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
235
|
+
{
|
|
236
|
+
...FINANCIAL_PERIOD_OPTION,
|
|
237
|
+
description: "Daily multiples, or one observation per reporting period.",
|
|
238
|
+
values: [{ value: "daily", aliases: ["d"] }, ...FINANCIAL_PERIOD_OPTION.values!, { value: "ttm" }],
|
|
239
|
+
defaultValue: "daily",
|
|
240
|
+
},
|
|
241
|
+
{ ...FINANCIAL_PERIOD_COUNT_OPTION, description: "Keep only the latest N observations per ticker." },
|
|
225
242
|
],
|
|
226
243
|
discovery: {
|
|
227
244
|
id: "valuation-series",
|
|
@@ -37,7 +37,7 @@ import { futuresSessionOpenMinute } from "../../../market-data/market/trading-se
|
|
|
37
37
|
import { cleanFloat32Price } from "../../../cli/history-rows";
|
|
38
38
|
import { parseChartSpec } from "./chart-spec";
|
|
39
39
|
import { paneSchemas } from "./headless-schema";
|
|
40
|
-
import { barHistoryFreshness, REPORTED_DATA } from "../shared/report-freshness";
|
|
40
|
+
import { barHistoryFreshness, DAILY_CLOSES, REPORTED_DATA } from "../shared/report-freshness";
|
|
41
41
|
import type { BarPeriodToDate } from "../../../market-data/market/bar-period";
|
|
42
42
|
import { closedHistoryBarDay, formingHistoryBar } from "../ticker-detail/data-panes/history-bars";
|
|
43
43
|
import { inferSeriesFrequency } from "../../../series/csv";
|
|
@@ -151,6 +151,7 @@ export async function loadChartPaneModel(
|
|
|
151
151
|
},
|
|
152
152
|
resolveCapabilitySeries: headlessCapabilityResolver(context),
|
|
153
153
|
loadFuturesPriceUnit: (symbol) => loadFuturesPriceUnit(symbol, (key) => loadFuturesContractTerms(key, context.apiClient)),
|
|
154
|
+
loadEarningsHistory: (symbol) => context.apiClient.getCloudEarningsHistory(symbol, 100),
|
|
154
155
|
loadFredSeries: async (request) => ({
|
|
155
156
|
data: await context.apiClient.getCloudFredSeries(request.seriesId, {
|
|
156
157
|
startDate: request.startDate,
|
|
@@ -221,9 +222,14 @@ export async function loadChartPaneModel(
|
|
|
221
222
|
kind, source.fieldId.split(".")[1]!, source.period === "quarterly" ? "quarterly" : "annual",
|
|
222
223
|
source.instrument.symbol,
|
|
223
224
|
).map((row) => [row.date, row.growth])) : null;
|
|
225
|
+
// A formula or a converted price prints at its legs' decimals in text; JSON keeps every digit.
|
|
226
|
+
const { valueDecimals, ...reported } = series;
|
|
224
227
|
return {
|
|
225
|
-
...
|
|
228
|
+
...reported,
|
|
226
229
|
...name ? { name } : {},
|
|
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 } : {},
|
|
227
233
|
derived: !ids.has(series.id),
|
|
228
234
|
points: series.points.map((point) => ({
|
|
229
235
|
...point,
|
|
@@ -247,7 +253,7 @@ export async function loadChartPaneModel(
|
|
|
247
253
|
...(chart.priceHistoryIntegrity?.length ? { priceHistoryIntegrity: chart.priceHistoryIntegrity } : {}),
|
|
248
254
|
...(valuationPriceIssues.length ? { valuationPriceIssues } : {}),
|
|
249
255
|
...(periodCoverage.length ? { periodCoverage } : {}),
|
|
250
|
-
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
|
|
251
257
|
|| chart.delistings?.some((delisting) => delisting.notice === warning)), ...integrityNotices],
|
|
252
258
|
priceComparison: chart.priceComparison ?? null,
|
|
253
259
|
summaries: chart.series.filter((series) => !series.profile).map((series) => ({ id: series.id, ...summarizeResolvedSeries(series) })),
|
|
@@ -346,6 +352,13 @@ function chartFreshness(
|
|
|
346
352
|
// A leg that failed to load has no bars to date; the ones that loaded say what the chart is.
|
|
347
353
|
const loaded = allSeries.filter((entry) => entry.points.length > 0);
|
|
348
354
|
const series = loaded.length ? loaded : allSeries;
|
|
355
|
+
const visible = spec.series.filter((entry) => entry.visible !== false);
|
|
356
|
+
if (visible.length && series.every(entry => (entry as HeadlessSeries & Pick<ResolvedSeries, "nativeFrequency">).nativeFrequency === "daily")
|
|
357
|
+
&& visible.every((entry) => entry.source.kind === "security"
|
|
358
|
+
&& entry.source.period === "daily" && entry.source.fieldId.startsWith("valuation."))) {
|
|
359
|
+
const ends = series.flatMap((entry) => entry.points.at(-1)?.date ?? []).map((date) => new Date(date).toISOString().slice(0, 10)).sort();
|
|
360
|
+
return { ...DAILY_CLOSES, ...(ends.length ? { asOf: ends.at(-1)!, oldest: ends[0]! } : {}) };
|
|
361
|
+
}
|
|
349
362
|
const resolutions = series.map((entry) => (entry as HeadlessSeries & { historyResolution?: string }).historyResolution);
|
|
350
363
|
const dataset = isDatasetChart(spec);
|
|
351
364
|
if (!series.length || resolutions.some((resolution) => !resolution)) {
|
|
@@ -712,6 +725,9 @@ export function chartHeadless(template: keyof typeof paneSchemas): HeadlessPaneD
|
|
|
712
725
|
}, context.marketData) };
|
|
713
726
|
}
|
|
714
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];
|
|
715
731
|
if (template === "graph-intraday-price-pane" && !intradayHistories.length && model.chart.viewport) {
|
|
716
732
|
const { start, end } = model.chart.viewport;
|
|
717
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",
|
|
@@ -54,8 +54,10 @@ import {
|
|
|
54
54
|
} from "../../../state/app/context";
|
|
55
55
|
import { colors } from "../../../theme/colors";
|
|
56
56
|
import { isUsListingExchange, parsePublicTickerKey, publicTickerKey } from "../../../utils/exchanges";
|
|
57
|
-
import { isMarketFieldId } from "../../../time-series/field-catalog";
|
|
58
|
-
import { isDatasetChart } from "../../../time-series/spec";
|
|
57
|
+
import { getTimeSeriesField, isMarketFieldId } from "../../../time-series/field-catalog";
|
|
58
|
+
import { isDatasetChart, isOhlcSeriesStyle } from "../../../time-series/spec";
|
|
59
|
+
import { useThemeColors } from "../../../theme/theme-context";
|
|
60
|
+
import { priceComparisonSeriesIds } from "../../../time-series/price-comparison";
|
|
59
61
|
import { seriesMessageName, studyMessageName } from "../../../time-series/series-name";
|
|
60
62
|
import { CHART_COMPOSER_PANE_ID } from "../../../types/config";
|
|
61
63
|
import { useRemoteUiNode } from "../../../remote/semantic-tree";
|
|
@@ -117,6 +119,8 @@ import {
|
|
|
117
119
|
CHART_BAR_SIZE_PRESETS,
|
|
118
120
|
CHART_RANGES as RANGES,
|
|
119
121
|
CHART_RESOLUTIONS,
|
|
122
|
+
CHART_CANDLE_COLOURS_SETTING_KEY,
|
|
123
|
+
chartCandleColours,
|
|
120
124
|
chartStudyOptionsFor,
|
|
121
125
|
chartStudyPeriodTitle,
|
|
122
126
|
chartStudySettingLabel,
|
|
@@ -342,6 +346,14 @@ function ChartComposerSurface({
|
|
|
342
346
|
// plausible time is dropped without pinning. This handle only sets the
|
|
343
347
|
// pin; the chart it opens on is what shows and keeps it.
|
|
344
348
|
const [pinnedMoment, setPinnedMoment] = usePaneSettingValue<unknown>(CHART_PINNED_MOMENT_SETTING_KEY, null);
|
|
349
|
+
const [candleColoursSetting] = usePaneSettingValue<unknown>(CHART_CANDLE_COLOURS_SETTING_KEY, null);
|
|
350
|
+
const gainColour = useThemeColors().positive;
|
|
351
|
+
// Rising candles in the gain colour; falling ones already take the loss colour.
|
|
352
|
+
const candleColour = useCallback((list: ResolvedSeries[]) => (
|
|
353
|
+
chartCandleColours(candleColoursSetting) === "change"
|
|
354
|
+
? list.map((entry) => isOhlcSeriesStyle(entry.style) ? { ...entry, color: gainColour } : entry)
|
|
355
|
+
: list
|
|
356
|
+
), [candleColoursSetting, gainColour]);
|
|
345
357
|
const momentLink = useTabCursorLink(paneId ? `${paneId}:moment` : null, { viewer: false });
|
|
346
358
|
useEffect(() => {
|
|
347
359
|
if (pinnedMoment == null || !momentLink?.pin) return;
|
|
@@ -446,6 +458,14 @@ function ChartComposerSurface({
|
|
|
446
458
|
const selectedPairStudies = getSelectedPairStudies(spec);
|
|
447
459
|
const viewport = resolution.viewport;
|
|
448
460
|
const baseSeriesIds = useMemo(() => new Set(spec.series.map((series) => series.id)), [spec.series]);
|
|
461
|
+
const rangeStatisticsSeriesId = spec.series.length === 1
|
|
462
|
+
&& spec.series[0]!.source.kind === "security"
|
|
463
|
+
&& spec.series[0]!.source.fieldId.startsWith("valuation.")
|
|
464
|
+
&& spec.series[0]!.source.period === "daily"
|
|
465
|
+
&& resolution.series.find(series => series.id === spec.series[0]!.id)?.nativeFrequency === "daily"
|
|
466
|
+
&& spec.series[0]!.transform === "raw"
|
|
467
|
+
&& spec.series[0]!.visible !== false
|
|
468
|
+
? spec.series[0]!.id : undefined;
|
|
449
469
|
// Hidden series are never loaded, so the resolver has nothing to report for
|
|
450
470
|
// them. Without a placeholder they vanish from the legend entirely and the
|
|
451
471
|
// only way back is the series dialog.
|
|
@@ -453,6 +473,12 @@ function ChartComposerSurface({
|
|
|
453
473
|
// Each security series names its company once, beside its label: "AAPL Price · Apple".
|
|
454
474
|
const named = new Set<string>();
|
|
455
475
|
const resolved = (resolution.legendSeries ?? []).map((entry) => {
|
|
476
|
+
const authored = spec.series.find((series) => series.id === entry.id);
|
|
477
|
+
if (authored?.source.kind === "security" && authored.source.fieldId.startsWith("valuation.")) {
|
|
478
|
+
const field = getTimeSeriesField(authored.source.fieldId);
|
|
479
|
+
return { ...entry, label: authored.label?.trim()
|
|
480
|
+
|| `${authored.source.instrument.symbol} ${field?.shortLabel ?? "Multiple"}` };
|
|
481
|
+
}
|
|
456
482
|
const name = nameSeries ? listings.names.get(entry.id) : undefined;
|
|
457
483
|
const company = name ? shortCompanyName(name) : "";
|
|
458
484
|
if (!company || named.has(company)) return entry;
|
|
@@ -461,9 +487,9 @@ function ChartComposerSurface({
|
|
|
461
487
|
});
|
|
462
488
|
const resolvedIds = new Set(resolved.map((entry) => entry.id));
|
|
463
489
|
const missing = spec.series.filter((entry) => !resolvedIds.has(entry.id));
|
|
464
|
-
if (missing.length === 0) return
|
|
490
|
+
if (missing.length === 0) return candleColour(resolved);
|
|
465
491
|
const bySpecOrder = new Map(spec.series.map((entry, index) => [entry.id, index] as const));
|
|
466
|
-
return [
|
|
492
|
+
return candleColour([
|
|
467
493
|
...resolved,
|
|
468
494
|
...missing.map((entry): ResolvedSeries => ({
|
|
469
495
|
id: entry.id,
|
|
@@ -481,15 +507,15 @@ function ChartComposerSurface({
|
|
|
481
507
|
hidden: true,
|
|
482
508
|
points: [],
|
|
483
509
|
})),
|
|
484
|
-
].sort((a, b) => (bySpecOrder.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (bySpecOrder.get(b.id) ?? Number.MAX_SAFE_INTEGER));
|
|
485
|
-
}, [listings.names, nameSeries, resolution.legendSeries, spec.series]);
|
|
510
|
+
].sort((a, b) => (bySpecOrder.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (bySpecOrder.get(b.id) ?? Number.MAX_SAFE_INTEGER)));
|
|
511
|
+
}, [candleColour, listings.names, nameSeries, resolution.legendSeries, spec.series]);
|
|
486
512
|
const plottedSeries = useMemo(
|
|
487
|
-
() => projectVisibleChartSeries(
|
|
513
|
+
() => candleColour(projectVisibleChartSeries(
|
|
488
514
|
spec,
|
|
489
515
|
resolution.bufferedSeries ?? resolution.series,
|
|
490
516
|
resolution.legendSeries,
|
|
491
|
-
),
|
|
492
|
-
[resolution.bufferedSeries, resolution.legendSeries, resolution.series, spec],
|
|
517
|
+
)),
|
|
518
|
+
[candleColour, resolution.bufferedSeries, resolution.legendSeries, resolution.series, spec],
|
|
493
519
|
);
|
|
494
520
|
const { notify } = usePluginAppActions();
|
|
495
521
|
const { runtime } = usePluginRenderContext();
|
|
@@ -1176,6 +1202,22 @@ function ChartComposerSurface({
|
|
|
1176
1202
|
}]
|
|
1177
1203
|
: [];
|
|
1178
1204
|
|
|
1205
|
+
// Returns of two or more prices compare as price returns; the toggle reinvests their cash distributions.
|
|
1206
|
+
const totalReturnOn = spec.viewport.totalReturn === true;
|
|
1207
|
+
const totalReturnFilters = totalReturnOn || priceComparisonSeriesIds(spec) !== null
|
|
1208
|
+
? [{
|
|
1209
|
+
id: "total-return",
|
|
1210
|
+
kind: "toggle" as const,
|
|
1211
|
+
label: "Total return",
|
|
1212
|
+
short: "Total ret",
|
|
1213
|
+
value: totalReturnOn,
|
|
1214
|
+
onChange: (value: boolean) => {
|
|
1215
|
+
const { totalReturn: _previous, ...viewport } = specRef.current.viewport;
|
|
1216
|
+
updateSpec({ ...specRef.current, viewport: value ? { ...viewport, totalReturn: true } : viewport });
|
|
1217
|
+
},
|
|
1218
|
+
}]
|
|
1219
|
+
: [];
|
|
1220
|
+
|
|
1179
1221
|
// A company that reports no quarterly figures says so, and where its annual ones are.
|
|
1180
1222
|
const unreported = resolution.legendSeries?.find((entry) => entry.unreportedPeriod && entry.points.length === 0);
|
|
1181
1223
|
const emptyMessage = spec.series.length === 0
|
|
@@ -1202,6 +1244,7 @@ function ChartComposerSurface({
|
|
|
1202
1244
|
onChange: (value: string) => setRange(value as TimeRange) },
|
|
1203
1245
|
...genericFilters,
|
|
1204
1246
|
...extendedHoursFilters,
|
|
1247
|
+
...totalReturnFilters,
|
|
1205
1248
|
]}
|
|
1206
1249
|
view={resolutionOptions.length > 1 ? {
|
|
1207
1250
|
value: spec.viewport.resolution,
|
|
@@ -1272,6 +1315,7 @@ function ChartComposerSurface({
|
|
|
1272
1315
|
timePick={timePick}
|
|
1273
1316
|
pricePick={pricePick}
|
|
1274
1317
|
levels={chartLevels}
|
|
1318
|
+
rangeStatisticsSeriesId={rangeStatisticsSeriesId}
|
|
1275
1319
|
emptyMessage={emptyMessage}
|
|
1276
1320
|
emptyDetail={emptyDetail}
|
|
1277
1321
|
legendAccessory={(
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from "../../../time-series/types";
|
|
7
7
|
import type { ChartResolution, TimeRange } from "../../../components/chart/core/types";
|
|
8
8
|
import { MAX_CHART_COMPOSER_SERIES } from "./chart-spec";
|
|
9
|
-
import { appendChartSeries, appendPlacedSeries, buildCustomSeries, buildSeriesSpec, panelsForSeries } from "./chart-spec-edit";
|
|
9
|
+
import { appendChartSeries, appendPlacedSeries, buildCustomSeries, buildSeriesSpec, nextGeneratedPanelId, panelsForSeries } from "./chart-spec-edit";
|
|
10
10
|
import { currencyPairSpelling } from "../../../utils/currency-pair";
|
|
11
11
|
import { dashedCoinPair } from "../../../utils/crypto-pair";
|
|
12
12
|
import {
|
|
@@ -82,21 +82,51 @@ function placeFormulaLeg(formula: ParsedFormula, index: number, series: ChartSer
|
|
|
82
82
|
return mixed && series.panelId === "main" && !formula.legs[index]!.perBarrel ? { ...series, axis: "right" } : series;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* A series the formula is divided or multiplied by (an FX rate) counts
|
|
87
|
+
* something else again: it is drawn in a panel of its own, not on the legs' axes.
|
|
88
|
+
*/
|
|
89
|
+
function placeFormulaFactor(spec: Pick<ChartSpec, "panels" | "series" | "studies">, series: ChartSeriesSpec): ChartSeriesSpec {
|
|
90
|
+
return { ...series, panelId: nextGeneratedPanelId(spec), axis: "auto" };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A factor's panel is shorter than a series panel, so the legs and the formula keep their room. */
|
|
94
|
+
const FACTOR_PANEL_HEIGHT = 0.2;
|
|
95
|
+
|
|
96
|
+
/** The panels with each new factor's last and short: the formula reads right under its legs. */
|
|
97
|
+
function withFactorPanels(panels: ChartSpec["panels"], factors: readonly ChartSeriesSpec[]): ChartSpec["panels"] {
|
|
98
|
+
const ids = new Set(factors.map((series) => series.panelId));
|
|
99
|
+
return [
|
|
100
|
+
...panels.filter((panel) => !ids.has(panel.id)),
|
|
101
|
+
...panels.filter((panel) => ids.has(panel.id)).map((panel) => ({ ...panel, height: FACTOR_PANEL_HEIGHT })),
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The study that weighs a formula's legs and scales the sum, charted as the given series, in the Formula panel. */
|
|
86
106
|
function formulaStudy(formula: ParsedFormula, inputSeriesIds: string[]): ChartStudySpec {
|
|
87
107
|
const parameters: ChartStudySpec["parameters"] = { divisor: formula.divisor };
|
|
88
108
|
formula.legs.forEach((leg, index) => {
|
|
89
109
|
parameters[`weight${index + 1}`] = leg.weight;
|
|
90
110
|
if (leg.perBarrel) parameters[`perBarrel${index + 1}`] = 1;
|
|
111
|
+
if (leg.perTonne) parameters[`perTonne${index + 1}`] = leg.perTonne;
|
|
112
|
+
});
|
|
113
|
+
formula.factors.forEach((factor, index) => {
|
|
114
|
+
parameters[`power${formula.legs.length + index + 1}`] = factor.power;
|
|
91
115
|
});
|
|
92
116
|
return { id: FORMULA_STUDY_ID, kind: "formula", inputSeriesIds, parameters, panelId: "formula", axis: "auto" };
|
|
93
117
|
}
|
|
94
118
|
|
|
95
|
-
/** A formula's legs as series, with the study that sums them in the Formula panel. */
|
|
119
|
+
/** A formula's legs and factors as series, with the study that sums them in the Formula panel. */
|
|
96
120
|
function formulaChartPreset(formula: ParsedFormula): ChartSpec {
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
121
|
+
const legs = buildCustomSeries([...formula.legs, ...formula.factors].map((entry) => entry.series));
|
|
122
|
+
const series: ChartSeriesSpec[] = [];
|
|
123
|
+
legs.forEach((entry, index) => {
|
|
124
|
+
series.push(index < formula.legs.length
|
|
125
|
+
? placeFormulaLeg(formula, index, entry)
|
|
126
|
+
: placeFormulaFactor({ panels: [], series, studies: [] }, entry));
|
|
127
|
+
});
|
|
128
|
+
const spec = chartSpec(series, { studies: [formulaStudy(formula, series.map((entry) => entry.id))] });
|
|
129
|
+
return { ...spec, panels: withFactorPanels(spec.panels, series.slice(formula.legs.length)) };
|
|
100
130
|
}
|
|
101
131
|
|
|
102
132
|
/**
|
|
@@ -107,17 +137,22 @@ function appendChartFormula(spec: ChartSpec, expression: string): { spec: ChartS
|
|
|
107
137
|
const formula = parseFormulaExpression(expression);
|
|
108
138
|
if (!formula) throw new Error(`"${expression}" is not a formula.`);
|
|
109
139
|
const key = (series: ChartSeriesSpec) => formatSeriesExpression(series).toLowerCase();
|
|
110
|
-
const
|
|
140
|
+
const terms = [...formula.legs, ...formula.factors];
|
|
141
|
+
const legs = terms.map((leg) => key(buildSeriesSpec(leg.series, 0)));
|
|
111
142
|
const missing = legs.filter((leg) => !spec.series.some((series) => key(series) === leg)).length;
|
|
112
143
|
if (spec.series.length + missing > MAX_CHART_COMPOSER_SERIES) {
|
|
113
144
|
throw new Error(`Charts support up to ${MAX_CHART_COMPOSER_SERIES} base series.`);
|
|
114
145
|
}
|
|
115
146
|
let next: ChartSpec = { ...spec, studies: spec.studies.filter((study) => study.id !== FORMULA_STUDY_ID) };
|
|
116
|
-
const
|
|
147
|
+
const placedFactors: ChartSeriesSpec[] = [];
|
|
148
|
+
const inputs = terms.map((leg, index) => {
|
|
117
149
|
const charted = next.series.find((series) => key(series) === legs[index]);
|
|
118
150
|
if (charted) return charted;
|
|
119
151
|
const appended = appendChartSeries(next, leg.series);
|
|
120
|
-
const placed =
|
|
152
|
+
const placed = index < formula.legs.length
|
|
153
|
+
? placeFormulaLeg(formula, index, appended.series)
|
|
154
|
+
: placeFormulaFactor(next, appended.series);
|
|
155
|
+
if (index >= formula.legs.length) placedFactors.push(placed);
|
|
121
156
|
next = { ...appended.spec, series: appended.spec.series.map((series) => series.id === placed.id ? placed : series) };
|
|
122
157
|
return placed;
|
|
123
158
|
});
|
|
@@ -127,7 +162,10 @@ function appendChartFormula(spec: ChartSpec, expression: string): { spec: ChartS
|
|
|
127
162
|
spec: {
|
|
128
163
|
...next,
|
|
129
164
|
studies,
|
|
130
|
-
panels:
|
|
165
|
+
panels: withFactorPanels(
|
|
166
|
+
[...next.panels, ...panelsForSeries(next.series, studies).filter((panel) => !known.has(panel.id))],
|
|
167
|
+
placedFactors,
|
|
168
|
+
),
|
|
131
169
|
},
|
|
132
170
|
series: inputs.at(-1)!,
|
|
133
171
|
};
|
|
@@ -275,11 +313,8 @@ export function buildValuationChartPreset(
|
|
|
275
313
|
const series = buildSeriesSpec(
|
|
276
314
|
{ kind: "security", ...instrument, fieldId: resolvedField },
|
|
277
315
|
index,
|
|
278
|
-
{ style:
|
|
316
|
+
{ style: "line", axis: "left" },
|
|
279
317
|
);
|
|
280
|
-
|
|
281
|
-
// reconstruct, so a quarterly trailing multiple has only its latest few
|
|
282
|
-
// quarters. Quarterly is the series' Period control away.
|
|
283
|
-
return series.source.kind === "security" ? { ...series, source: { ...series.source, period: "annual" } } : series;
|
|
318
|
+
return series.source.kind === "security" ? { ...series, source: { ...series.source, period: "daily" } } : series;
|
|
284
319
|
}));
|
|
285
320
|
}
|