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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { alignTimeSeries, effectiveTimeSeriesPointTime, scalarPointValue } from "./alignment";
|
|
2
2
|
import { mergePriceHistoryIntegrity } from "../utils/price-history-integrity";
|
|
3
|
-
import { resolveCurrencyUnit } from "../utils/currency-units";
|
|
3
|
+
import { resolveCurrencyUnit, US_CENTS } from "../utils/currency-units";
|
|
4
|
+
import { isCurrencyCode } from "../utils/currency-pair";
|
|
5
|
+
import { stablePriceFractionDigits } from "../market-data/market/format";
|
|
4
6
|
import { isRealizedVolatilityEstimator, realizedVolatilityCadenceIssue, rollingRealizedVolatility } from "../market-data/realized-volatility";
|
|
5
7
|
import { latestTradingSessionOpen } from "../market-data/market/trading-sessions";
|
|
6
8
|
import { CHART_RESOLUTION_STEP_MS, isIntradayResolution, type ManualChartResolution } from "./resolution";
|
|
@@ -19,7 +21,16 @@ import type {
|
|
|
19
21
|
import { isFiniteNumber } from "../utils/guards";
|
|
20
22
|
import { studyMessageName } from "./series-name";
|
|
21
23
|
import { dailyLeverageGrowth, isLeverageFactor, leverageDecayPoints, leverageFactorLabel, leverageSymbol, sessionCloses, type StudyAnchor } from "./leverage";
|
|
22
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
GALLONS_PER_BARREL,
|
|
26
|
+
isBarrelsPerTonne,
|
|
27
|
+
isPerBarrelUnit,
|
|
28
|
+
isPerGallonUnit,
|
|
29
|
+
perBarrelUnit,
|
|
30
|
+
perTonneUnit,
|
|
31
|
+
quantityIdentity,
|
|
32
|
+
unitCurrencyToken,
|
|
33
|
+
} from "./price-units";
|
|
23
34
|
|
|
24
35
|
export interface StudyResolutionResult {
|
|
25
36
|
series: ResolvedSeries[];
|
|
@@ -104,6 +115,8 @@ function outputSeries(
|
|
|
104
115
|
statedUnit?: string | null;
|
|
105
116
|
/** The kind of price the output is, for its decimals; by default the input's, while the output keeps its unit. */
|
|
106
117
|
priceAssetCategory?: string | null;
|
|
118
|
+
/** The decimals a value worked out from prices prints at. */
|
|
119
|
+
valueDecimals?: number;
|
|
107
120
|
},
|
|
108
121
|
): ResolvedSeries {
|
|
109
122
|
const historyResolution = options.historyResolution === undefined ? input.historyResolution : options.historyResolution;
|
|
@@ -116,6 +129,7 @@ function outputSeries(
|
|
|
116
129
|
unit: options.unit ?? input.unit,
|
|
117
130
|
unitGroup: options.unitGroup ?? input.unitGroup,
|
|
118
131
|
...(statedUnit ? { statedUnit } : {}),
|
|
132
|
+
...(options.valueDecimals !== undefined ? { valueDecimals: options.valueDecimals } : {}),
|
|
119
133
|
priceAssetCategory: options.priceAssetCategory === undefined
|
|
120
134
|
? sameUnit ? input.priceAssetCategory : undefined
|
|
121
135
|
: options.priceAssetCategory ?? undefined,
|
|
@@ -386,15 +400,21 @@ function resolveVolume(spec: ChartStudySpec, input: ResolvedSeries, color: strin
|
|
|
386
400
|
// No bar has volume to plot (spot FX quotes 0 for every bar; a coin's window
|
|
387
401
|
// can miss every bar's), so the study adds no panel rather than an empty one.
|
|
388
402
|
if (input.points.length > 0 && !input.points.some((point) => isFiniteNumber(point.volume) && point.volume !== 0)) return [];
|
|
403
|
+
// Left to its own colour, volume follows the candles: the price's colour for
|
|
404
|
+
// a bar that rose, the falling colour for one that fell. A colour picked for it stays flat.
|
|
405
|
+
const directional = !spec.color;
|
|
389
406
|
const points = [...input.points]
|
|
390
407
|
.sort((left, right) => left.date.getTime() - right.date.getTime())
|
|
391
408
|
.flatMap((point) => isFiniteNumber(point.volume)
|
|
392
|
-
? [
|
|
409
|
+
? [{
|
|
410
|
+
...derivedPoint({ point, value: point.volume }, point.volume),
|
|
411
|
+
...(directional && isFiniteNumber(point.open) && isFiniteNumber(point.close) && point.close < point.open ? { fell: true } : {}),
|
|
412
|
+
}]
|
|
393
413
|
: []);
|
|
394
414
|
return [outputSeries(spec, input, {
|
|
395
415
|
label: nameInput ? `Volume ${input.label}` : "Volume",
|
|
396
416
|
points,
|
|
397
|
-
color,
|
|
417
|
+
color: directional ? input.color : color,
|
|
398
418
|
unit: input.volumeUnit ?? "",
|
|
399
419
|
unitGroup: "volume",
|
|
400
420
|
style: "columns",
|
|
@@ -716,13 +736,20 @@ function seriesCurrency(series: ResolvedSeries): string | null {
|
|
|
716
736
|
function unitFactorIdentity(value: string): string {
|
|
717
737
|
// "US cents", as futures terms word a grain's quote, is the USX the quote names.
|
|
718
738
|
const token = unitCurrencyToken(value) ?? value.trim();
|
|
719
|
-
|
|
739
|
+
// Three letters are a currency unless they spell a quantity: "bbl" is a barrel.
|
|
740
|
+
if (/^[A-Za-z]{3}$/.test(token) && quantityIdentity(token) === token.toLowerCase()) {
|
|
720
741
|
const { currency, divisor } = resolveCurrencyUnit(token);
|
|
721
742
|
return `${currency}:${divisor}`;
|
|
722
743
|
}
|
|
723
744
|
return quantityIdentity(token);
|
|
724
745
|
}
|
|
725
746
|
|
|
747
|
+
/** Whether a unit's factor is money (USD, GBp, US cents) rather than a quantity it buys. */
|
|
748
|
+
function isCurrencyFactor(value: string): boolean {
|
|
749
|
+
const currency = /^([A-Z]{3}):\d+$/.exec(unitFactorIdentity(value))?.[1];
|
|
750
|
+
return !!currency && (currency === US_CENTS || isCurrencyCode(currency));
|
|
751
|
+
}
|
|
752
|
+
|
|
726
753
|
function knownUnitSignature(series: Pick<ResolvedSeries, "unit" | "unitGroup">): string | null {
|
|
727
754
|
const unit = series.unit.trim();
|
|
728
755
|
if (!unit || /(?:^|\W)(?:currency|unknown|unspecified|n\/a)(?:$|\W)/i.test(unit)
|
|
@@ -794,28 +821,41 @@ function unitMismatchMessage(
|
|
|
794
821
|
|
|
795
822
|
function unitFactors(unit: string): { numerator: string[]; denominator: string[] } {
|
|
796
823
|
const [numerator = "", ...denominator] = unit.split("/");
|
|
797
|
-
const factor = (value: string) => {
|
|
798
|
-
const normalized =
|
|
824
|
+
const factor = (value: string) => value.split("·").flatMap((part) => {
|
|
825
|
+
const normalized = part.trim();
|
|
799
826
|
if (!normalized || normalized === "1") return [];
|
|
800
827
|
return [normalized.toLowerCase() === "shares" ? "share" : normalized];
|
|
801
|
-
};
|
|
828
|
+
});
|
|
802
829
|
return {
|
|
803
830
|
numerator: factor(numerator),
|
|
804
831
|
denominator: denominator.flatMap(factor),
|
|
805
832
|
};
|
|
806
833
|
}
|
|
807
834
|
|
|
808
|
-
|
|
835
|
+
interface SeriesUnit {
|
|
809
836
|
unit: string;
|
|
810
837
|
unitGroup: string;
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
const UNKNOWN_UNIT: SeriesUnit = { unit: "unknown", unitGroup: "derived-unit:unknown" };
|
|
841
|
+
|
|
842
|
+
function isPriceGroup(unitGroup: string): boolean {
|
|
843
|
+
return /^price(?::|$)/.test(unitGroup);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* The unit of `left` times (`power` 1) or over (-1) `right`, the factors they
|
|
848
|
+
* share cancelling: USD / barrel over an FX rate's USD / EUR is EUR / barrel.
|
|
849
|
+
* Units are spaced as either side writes them ("USD / gallon"). A price over
|
|
850
|
+
* or times a price that leaves one currency per one quantity is a price in
|
|
851
|
+
* that currency; anything else is a derived unit, or a plain ratio.
|
|
852
|
+
*/
|
|
853
|
+
function combinedUnit(left: SeriesUnit, right: SeriesUnit, power: 1 | -1): SeriesUnit {
|
|
854
|
+
if (!knownUnitSignature(left) || !knownUnitSignature(right)) return UNKNOWN_UNIT;
|
|
815
855
|
const leftFactors = unitFactors(left.unit);
|
|
816
856
|
const rightFactors = unitFactors(right.unit);
|
|
817
|
-
const numerator = [...leftFactors.numerator, ...rightFactors.denominator];
|
|
818
|
-
const denominator = [...leftFactors.denominator, ...rightFactors.numerator];
|
|
857
|
+
const numerator = [...leftFactors.numerator, ...(power < 0 ? rightFactors.denominator : rightFactors.numerator)];
|
|
858
|
+
const denominator = [...leftFactors.denominator, ...(power < 0 ? rightFactors.numerator : rightFactors.denominator)];
|
|
819
859
|
for (let index = numerator.length - 1; index >= 0; index -= 1) {
|
|
820
860
|
const match = denominator.findIndex((factor) => (
|
|
821
861
|
unitFactorIdentity(factor) === unitFactorIdentity(numerator[index]!)
|
|
@@ -824,9 +864,14 @@ function ratioUnit(left: ResolvedSeries, right: ResolvedSeries): {
|
|
|
824
864
|
numerator.splice(index, 1);
|
|
825
865
|
denominator.splice(match, 1);
|
|
826
866
|
}
|
|
867
|
+
const slash = [left.unit, right.unit].some((unit) => /\s\/\s/.test(unit)) ? " / " : "/";
|
|
827
868
|
const unit = denominator.length === 0
|
|
828
869
|
? numerator.join("·") || "x"
|
|
829
|
-
: `${numerator.join("·") || "1"}
|
|
870
|
+
: `${numerator.join("·") || "1"}${slash}${denominator.join("·")}`;
|
|
871
|
+
if (isPriceGroup(left.unitGroup) && isPriceGroup(right.unitGroup) && numerator.length === 1 && isCurrencyFactor(numerator[0]!)
|
|
872
|
+
&& (denominator.length === 0 || (denominator.length === 1 && !isCurrencyFactor(denominator[0]!)))) {
|
|
873
|
+
return { unit, unitGroup: `price:${unitCurrencyToken(numerator[0]!) ?? numerator[0]!}` };
|
|
874
|
+
}
|
|
830
875
|
const groupFactor = (value: string) => {
|
|
831
876
|
const identity = unitFactorIdentity(value);
|
|
832
877
|
return (/^[A-Za-z]{3}$/.test(value.trim()) ? identity.replace(/:1$/, "") : identity).toLowerCase();
|
|
@@ -840,6 +885,78 @@ function ratioUnit(left: ResolvedSeries, right: ResolvedSeries): {
|
|
|
840
885
|
};
|
|
841
886
|
}
|
|
842
887
|
|
|
888
|
+
function ratioUnit(left: SeriesUnit, right: SeriesUnit): SeriesUnit {
|
|
889
|
+
return combinedUnit(left, right, -1);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/** An FX rate's unit: one currency per another, as USD / EUR. */
|
|
893
|
+
function isRateUnit(unit: string): boolean {
|
|
894
|
+
const { numerator, denominator } = unitFactors(unit);
|
|
895
|
+
return numerator.length === 1 && denominator.length === 1 && isCurrencyFactor(numerator[0]!) && isCurrencyFactor(denominator[0]!);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Why a price times (`power` 1) or over (-1) an FX rate is no price, with how
|
|
900
|
+
* to convert it instead: a crack in USD / barrel over USDJPY=X (JPY / USD) is
|
|
901
|
+
* USD·USD / barrel·JPY, and times it JPY / barrel. A ratio, which cannot
|
|
902
|
+
* multiply, names the formula to write (`priceName` its numerator). Null when
|
|
903
|
+
* the rate converts the price, or either is not what this asks about.
|
|
904
|
+
*/
|
|
905
|
+
function fxConversionIssue(price: SeriesUnit, rate: ResolvedSeries, power: 1 | -1, result: SeriesUnit, priceName?: string): string | null {
|
|
906
|
+
if (!isPriceGroup(price.unitGroup) || !isPriceGroup(rate.unitGroup) || !isRateUnit(rate.unit) || isPriceGroup(result.unitGroup)) {
|
|
907
|
+
return null;
|
|
908
|
+
}
|
|
909
|
+
const name = legName(rate);
|
|
910
|
+
const done = `${power < 0 ? "Dividing" : "Multiplying"} by ${name} (${rate.unit}) leaves ${result.unit}`;
|
|
911
|
+
const other = combinedUnit(price, rate, power < 0 ? 1 : -1);
|
|
912
|
+
if (isPriceGroup(other.unitGroup)) {
|
|
913
|
+
const fix = priceName ? `write (${priceName})${power < 0 ? "*" : "/"}${name}` : `${power < 0 ? "multiply" : "divide"} by ${name}`;
|
|
914
|
+
return `${done}; ${fix} instead to get ${other.unit}.`;
|
|
915
|
+
}
|
|
916
|
+
const currency = price.unitGroup.slice("price:".length) || "its currency";
|
|
917
|
+
return `${done}; ${price.unit} converts only with a rate that has ${currency} on one side.`;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/** The distinct currencies a unit still counts, once its factors cancel. */
|
|
921
|
+
function unitCurrencies(unit: string): Set<string> {
|
|
922
|
+
const { numerator, denominator } = unitFactors(unit);
|
|
923
|
+
return new Set([...numerator, ...denominator].filter(isCurrencyFactor).map((factor) => unitFactorIdentity(factor).split(":")[0]!));
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* The decimals a value worked out from prices prints at: the most its legs
|
|
928
|
+
* print, from two to four, and no more than a value of its own size needs:
|
|
929
|
+
* four under 10, two from 10. A crack of 4.9842 USD / gallon heating oil and
|
|
930
|
+
* 116.72 USD / barrel Brent reads 92.62; a gasoline-heating oil spread reads
|
|
931
|
+
* 1.7726. Undefined when a leg is not a price or the value has none yet.
|
|
932
|
+
*/
|
|
933
|
+
function derivedValueDecimals(inputs: readonly ResolvedSeries[], points: readonly TimeSeriesPoint[]): number | undefined {
|
|
934
|
+
let digits = 0;
|
|
935
|
+
for (const input of inputs) {
|
|
936
|
+
if (!isPriceGroup(input.unitGroup) || input.transform !== "raw") return undefined;
|
|
937
|
+
const reference = latestScalarValue(input.points);
|
|
938
|
+
if (reference === undefined) return undefined;
|
|
939
|
+
digits = Math.max(digits, stablePriceFractionDigits({
|
|
940
|
+
assetCategory: input.priceAssetCategory,
|
|
941
|
+
currency: unitCurrencyToken(input.unit.split("/")[0] ?? "") ?? undefined,
|
|
942
|
+
referencePrice: reference,
|
|
943
|
+
sessionPrices: [reference],
|
|
944
|
+
}));
|
|
945
|
+
}
|
|
946
|
+
const size = latestScalarValue(points);
|
|
947
|
+
if (size === undefined) return undefined;
|
|
948
|
+
return Math.min(size < 10 ? 4 : 2, Math.max(2, digits));
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/** The size of a series' last nonzero value, which its decimals are read from. */
|
|
952
|
+
function latestScalarValue(points: readonly TimeSeriesPoint[]): number | undefined {
|
|
953
|
+
for (let index = points.length - 1; index >= 0; index -= 1) {
|
|
954
|
+
const value = scalarPointValue(points[index]!);
|
|
955
|
+
if (value !== null && Number.isFinite(value) && value !== 0) return Math.abs(value);
|
|
956
|
+
}
|
|
957
|
+
return undefined;
|
|
958
|
+
}
|
|
959
|
+
|
|
843
960
|
function pairedSamples(left: ResolvedSeries, right: ResolvedSeries, carryForward: boolean): PairedSample[] {
|
|
844
961
|
return alignTimeSeries([left, right], {
|
|
845
962
|
mode: "intersection",
|
|
@@ -897,7 +1014,13 @@ function resolvePairStudy(
|
|
|
897
1014
|
const outputUnit = ratioStudy
|
|
898
1015
|
? ratioUnit(left, right)
|
|
899
1016
|
: { unit: left.unit, unitGroup: left.unitGroup };
|
|
1017
|
+
// A price over an FX rate is a price in the other currency (EUR / barrel),
|
|
1018
|
+
// stated as a future's unit is; a plain ratio keeps its own decimals.
|
|
1019
|
+
const convertedPrice = ratioStudy && isPriceGroup(outputUnit.unitGroup);
|
|
1020
|
+
const valueDecimals = !ratioStudy || convertedPrice ? derivedValueDecimals([left, right], points) : undefined;
|
|
900
1021
|
return [outputSeries(spec, left, {
|
|
1022
|
+
...(convertedPrice ? { statedUnit: outputUnit.unit, priceAssetCategory: null } : {}),
|
|
1023
|
+
...(valueDecimals !== undefined ? { valueDecimals } : {}),
|
|
901
1024
|
label: ratioStudy
|
|
902
1025
|
? `${left.label} / ${right.label}`
|
|
903
1026
|
: `${left.label} - ${multiplier === 1 ? "" : `${multiplier}×`}${right.label}`,
|
|
@@ -961,57 +1084,91 @@ function resolvePairStudy(
|
|
|
961
1084
|
}
|
|
962
1085
|
|
|
963
1086
|
/** Formulas of levels, which carry each input's latest value to the other inputs' dates. */
|
|
1087
|
+
/** The study's own colour, or the next after it no series or formula on the chart has taken. */
|
|
1088
|
+
function freeStudyColor(index: number, used: ReadonlySet<string>): string {
|
|
1089
|
+
for (let offset = 0; offset < STUDY_COLORS.length; offset += 1) {
|
|
1090
|
+
const color = STUDY_COLORS[(index + offset) % STUDY_COLORS.length]!;
|
|
1091
|
+
if (!used.has(color.toLowerCase())) return color;
|
|
1092
|
+
}
|
|
1093
|
+
return STUDY_COLORS[index % STUDY_COLORS.length]!;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
964
1096
|
function isLevelFormula(kind: ChartStudyKind): boolean {
|
|
965
1097
|
return kind === "ratio" || kind === "spread" || kind === "formula";
|
|
966
1098
|
}
|
|
967
1099
|
|
|
1100
|
+
interface FormulaLeg {
|
|
1101
|
+
/** The input's place in the study's inputs. */
|
|
1102
|
+
index: number;
|
|
1103
|
+
/** Its multiplier, its sign included. */
|
|
1104
|
+
weight: number;
|
|
1105
|
+
/** The multiplier carries the 42 gallons that make a per-gallon price per barrel. */
|
|
1106
|
+
perBarrel: boolean;
|
|
1107
|
+
/** The barrels in a tonne the multiplier carries (7.45 for gasoil), making a per-barrel price per tonne. */
|
|
1108
|
+
perTonne: number | null;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
968
1111
|
interface FormulaTerms {
|
|
969
|
-
/**
|
|
970
|
-
|
|
971
|
-
/**
|
|
972
|
-
|
|
1112
|
+
/** The inputs the formula adds up. */
|
|
1113
|
+
legs: FormulaLeg[];
|
|
1114
|
+
/** The inputs the whole sum is multiplied (1) or divided (-1) by: `/EURUSD=X`. */
|
|
1115
|
+
factors: Array<{ index: number; power: 1 | -1 }>;
|
|
973
1116
|
divisor: number;
|
|
974
1117
|
}
|
|
975
1118
|
|
|
976
1119
|
/**
|
|
977
|
-
* A formula study's terms: `weight1`, `perBarrel1`, ... per
|
|
978
|
-
*
|
|
979
|
-
*
|
|
980
|
-
*
|
|
981
|
-
*
|
|
1120
|
+
* A formula study's terms: `weight1`, `perBarrel1`, `perTonne1` ... per
|
|
1121
|
+
* input it adds up, `power1` ... (1 or -1) per input it multiplies or divides
|
|
1122
|
+
* the sum by, and an optional `divisor`. A saved or hand-edited spec can carry
|
|
1123
|
+
* a multiplier or divisor no formula was written with (a value that is not a
|
|
1124
|
+
* number is dropped when the spec loads, so it arrives missing); that is an
|
|
1125
|
+
* error, not a silent 1.
|
|
982
1126
|
*/
|
|
983
1127
|
function formulaTerms(spec: ChartStudySpec, inputs: readonly ResolvedSeries[]): FormulaTerms | { error: string } {
|
|
984
|
-
const
|
|
1128
|
+
const legs: FormulaLeg[] = [];
|
|
1129
|
+
const factors: FormulaTerms["factors"] = [];
|
|
985
1130
|
for (const [index, input] of inputs.entries()) {
|
|
1131
|
+
const power = spec.parameters[`power${index + 1}`];
|
|
1132
|
+
if (power !== undefined) {
|
|
1133
|
+
if (power !== 1 && power !== -1) return { error: `The formula neither multiplies nor divides by ${legName(input)}; write the formula again.` };
|
|
1134
|
+
factors.push({ index, power });
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
986
1137
|
const weight = spec.parameters[`weight${index + 1}`];
|
|
987
1138
|
if (!isFiniteNumber(weight)) return { error: `The multiplier of ${legName(input)} is missing or not a number; write the formula again.` };
|
|
988
1139
|
if (weight === 0) return { error: `${legName(input)} has a multiplier of 0, which removes it; leave it out of the formula instead.` };
|
|
989
|
-
|
|
1140
|
+
const perTonne = spec.parameters[`perTonne${index + 1}`];
|
|
1141
|
+
legs.push({
|
|
1142
|
+
index,
|
|
1143
|
+
weight,
|
|
1144
|
+
perBarrel: spec.parameters[`perBarrel${index + 1}`] === 1,
|
|
1145
|
+
perTonne: isFiniteNumber(perTonne) && isBarrelsPerTonne(perTonne) ? perTonne : null,
|
|
1146
|
+
});
|
|
990
1147
|
}
|
|
1148
|
+
if (legs.length === 0) return { error: "The formula has no series to add up, only series it divides by; write the formula again." };
|
|
991
1149
|
const divisor = spec.parameters.divisor ?? 1;
|
|
992
1150
|
if (!isFiniteNumber(divisor)) return { error: "The formula's divisor is not a number; write the formula again." };
|
|
993
1151
|
if (divisor === 0) return { error: "A formula cannot be divided by 0." };
|
|
994
|
-
return {
|
|
995
|
-
weights,
|
|
996
|
-
perBarrel: inputs.map((_, index) => spec.parameters[`perBarrel${index + 1}`] === 1),
|
|
997
|
-
divisor,
|
|
998
|
-
};
|
|
1152
|
+
return { legs, factors, divisor };
|
|
999
1153
|
}
|
|
1000
1154
|
|
|
1001
|
-
/**
|
|
1002
|
-
|
|
1003
|
-
|
|
1155
|
+
/**
|
|
1156
|
+
* A leg's unit once its multiplier is applied: 42 gallons of a per-gallon
|
|
1157
|
+
* price are a barrel's worth, and 7.45 barrels of gasoil a tonne's.
|
|
1158
|
+
*/
|
|
1159
|
+
function formulaLegUnit(leg: FormulaLeg, input: ResolvedSeries): string {
|
|
1160
|
+
const unit = leg.perBarrel ? perBarrelUnit(input.unit) : input.unit;
|
|
1161
|
+
return leg.perTonne ? perTonneUnit(unit) : unit;
|
|
1004
1162
|
}
|
|
1005
1163
|
|
|
1006
1164
|
/** Why a formula's legs cannot be summed, or null when every leg counts the same known unit. */
|
|
1007
1165
|
function formulaUnitMismatch(terms: FormulaTerms, inputs: readonly ResolvedSeries[]): string | null {
|
|
1008
|
-
if (
|
|
1009
|
-
const
|
|
1010
|
-
const signatures =
|
|
1166
|
+
if (terms.legs.length < 2) return null;
|
|
1167
|
+
const legs = terms.legs.map((leg) => ({ series: inputs[leg.index]!, unit: formulaLegUnit(leg, inputs[leg.index]!) }));
|
|
1168
|
+
const signatures = legs.map(({ series, unit }) => knownUnitSignature({ unit, unitGroup: series.unitGroup }));
|
|
1011
1169
|
const other = signatures.findIndex((signature, index) => index > 0 && (signature === null || signature !== signatures[0]));
|
|
1012
1170
|
if (signatures[0] !== null && other < 0) return null;
|
|
1013
|
-
|
|
1014
|
-
return unitMismatchMessage("formula", { series: inputs[0]!, unit: units[0]! }, { series: inputs[right]!, unit: units[right]! });
|
|
1171
|
+
return unitMismatchMessage("formula", legs[0]!, legs[Math.max(1, other)]!);
|
|
1015
1172
|
}
|
|
1016
1173
|
|
|
1017
1174
|
function formatFormulaNumber(value: number): string {
|
|
@@ -1020,20 +1177,25 @@ function formatFormulaNumber(value: number): string {
|
|
|
1020
1177
|
|
|
1021
1178
|
/**
|
|
1022
1179
|
* A formula as the legend names it, from the terms it was written with:
|
|
1023
|
-
* `42×HO=F - CL=F`, `(2×42×RB=F + 42×HO=F - 3×CL=F)/3`.
|
|
1180
|
+
* `42×HO=F - CL=F`, `(2×42×RB=F + 42×HO=F - 3×CL=F)/3`, `(42×HO=F - BZ=F)/EURUSD=X`.
|
|
1024
1181
|
*/
|
|
1025
1182
|
function formulaLabel(inputs: readonly ResolvedSeries[], terms: FormulaTerms): string {
|
|
1026
|
-
const sum =
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1029
|
-
const
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
const term = [...factors, legName(
|
|
1033
|
-
if (
|
|
1034
|
-
return `${weight < 0 ? "-" : "+"} ${term}`;
|
|
1183
|
+
const sum = terms.legs.map((leg, position) => {
|
|
1184
|
+
const magnitude = Math.abs(leg.weight);
|
|
1185
|
+
// The conversions it carries are named as written: 7.45×42×HO=F.
|
|
1186
|
+
const conversions = [...(leg.perTonne ? [leg.perTonne] : []), ...(leg.perBarrel ? [GALLONS_PER_BARREL] : [])];
|
|
1187
|
+
const count = conversions.reduce((rest, factor) => rest / factor, magnitude);
|
|
1188
|
+
const factors = [...(Math.abs(count - 1) < 1e-9 ? [] : [formatFormulaNumber(count)]), ...conversions.map(String)];
|
|
1189
|
+
const term = [...factors, legName(inputs[leg.index]!)].join("×");
|
|
1190
|
+
if (position === 0) return leg.weight < 0 ? `-${term}` : term;
|
|
1191
|
+
return `${leg.weight < 0 ? "-" : "+"} ${term}`;
|
|
1035
1192
|
}).join(" ");
|
|
1036
|
-
|
|
1193
|
+
if (terms.divisor === 1 && terms.factors.length === 0) return sum;
|
|
1194
|
+
return [
|
|
1195
|
+
`(${sum})`,
|
|
1196
|
+
...(terms.divisor === 1 ? [] : [`/${formatFormulaNumber(terms.divisor)}`]),
|
|
1197
|
+
...terms.factors.map(({ index, power }) => `${power < 0 ? "/" : "×"}${legName(inputs[index]!)}`),
|
|
1198
|
+
].join("");
|
|
1037
1199
|
}
|
|
1038
1200
|
|
|
1039
1201
|
/** Every input's value on each date they share, each carried forward to the others' dates. */
|
|
@@ -1055,31 +1217,67 @@ function alignedSamples(inputs: readonly ResolvedSeries[]): Array<{ point: TimeS
|
|
|
1055
1217
|
});
|
|
1056
1218
|
}
|
|
1057
1219
|
|
|
1058
|
-
/**
|
|
1220
|
+
/**
|
|
1221
|
+
* A weighted sum of the legs over a divisor, in the unit the legs share once
|
|
1222
|
+
* converted, then times or over the series it is scaled by, in the unit
|
|
1223
|
+
* that arithmetic leaves: a crack in USD / barrel over EURUSD=X is EUR / barrel.
|
|
1224
|
+
*/
|
|
1225
|
+
/**
|
|
1226
|
+
* A formula's unit: its legs' once converted, then times or over each series
|
|
1227
|
+
* it is scaled by. Legs that count different units, or an FX rate the wrong
|
|
1228
|
+
* way up, are an error that says why: a crack over USDJPY=X is no price, and
|
|
1229
|
+
* times it JPY / barrel.
|
|
1230
|
+
*/
|
|
1231
|
+
function formulaUnit(terms: FormulaTerms, inputs: readonly ResolvedSeries[]): SeriesUnit | { error: string } {
|
|
1232
|
+
const mismatch = formulaUnitMismatch(terms, inputs);
|
|
1233
|
+
if (mismatch) return { error: mismatch };
|
|
1234
|
+
const first = inputs[terms.legs[0]!.index]!;
|
|
1235
|
+
let unit: SeriesUnit = { unit: formulaLegUnit(terms.legs[0]!, first), unitGroup: first.unitGroup };
|
|
1236
|
+
for (const factor of terms.factors) {
|
|
1237
|
+
const series = inputs[factor.index]!;
|
|
1238
|
+
const next = combinedUnit(unit, series, factor.power);
|
|
1239
|
+
const issue = fxConversionIssue(unit, series, factor.power, next);
|
|
1240
|
+
if (issue) return { error: issue };
|
|
1241
|
+
unit = next;
|
|
1242
|
+
}
|
|
1243
|
+
return unit;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1059
1246
|
function resolveFormula(
|
|
1060
1247
|
spec: ChartStudySpec,
|
|
1061
1248
|
inputs: readonly ResolvedSeries[],
|
|
1062
1249
|
terms: FormulaTerms,
|
|
1250
|
+
unit: SeriesUnit,
|
|
1063
1251
|
color: string,
|
|
1064
1252
|
): ResolvedSeries[] {
|
|
1065
|
-
const first = inputs[0]!;
|
|
1066
|
-
const unit = formulaLegUnits(terms, inputs)[0]!;
|
|
1253
|
+
const first = inputs[terms.legs[0]!.index]!;
|
|
1067
1254
|
// Rounded off the float's last digits: 42 × 4.6233 - 96.12 is 98.0586, not 98.05860000000001.
|
|
1068
|
-
const points = alignedSamples(inputs).map(({ point, values }) =>
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1255
|
+
const points = alignedSamples(inputs).map(({ point, values }) => {
|
|
1256
|
+
let value: number | null = terms.legs.reduce((sum, leg) => sum + values[leg.index]! * leg.weight, 0) / terms.divisor;
|
|
1257
|
+
for (const factor of terms.factors) {
|
|
1258
|
+
const scale = values[factor.index]!;
|
|
1259
|
+
value = value === null || (factor.power < 0 && scale === 0) ? null : factor.power < 0 ? value / scale : value * scale;
|
|
1260
|
+
}
|
|
1261
|
+
return derivedPoint({ point, value: 0 }, value === null ? null : Number(value.toPrecision(12)));
|
|
1262
|
+
});
|
|
1072
1263
|
const frequencies = new Set(inputs.map((input) => input.nativeFrequency));
|
|
1073
1264
|
const resolutions = new Set(inputs.map((input) => input.historyResolution));
|
|
1265
|
+
const scaled = terms.factors.length > 0;
|
|
1266
|
+
// A price per quantity reads with its unit beside it, as a future's does.
|
|
1267
|
+
const statedUnit = scaled
|
|
1268
|
+
? isPriceGroup(unit.unitGroup) && unit.unit.includes("/") ? unit.unit : null
|
|
1269
|
+
: inputs.some((input) => input.statedUnit) ? unit.unit : null;
|
|
1270
|
+
const valueDecimals = !scaled || isPriceGroup(unit.unitGroup) ? derivedValueDecimals(inputs, points) : undefined;
|
|
1074
1271
|
return [outputSeries(spec, first, {
|
|
1075
1272
|
label: formulaLabel(inputs, terms),
|
|
1076
1273
|
points,
|
|
1077
1274
|
color,
|
|
1078
|
-
unit,
|
|
1079
|
-
unitGroup:
|
|
1080
|
-
statedUnit
|
|
1081
|
-
// A weighted sum is no traded price: its decimals are
|
|
1275
|
+
unit: unit.unit,
|
|
1276
|
+
unitGroup: unit.unitGroup,
|
|
1277
|
+
statedUnit,
|
|
1278
|
+
// A weighted sum is no traded price: its decimals are its legs', not a contract's tick.
|
|
1082
1279
|
priceAssetCategory: null,
|
|
1280
|
+
...(valueDecimals !== undefined ? { valueDecimals } : {}),
|
|
1083
1281
|
// As the pair formulas: as-of levels, constant until an input changes.
|
|
1084
1282
|
style: "step",
|
|
1085
1283
|
interpolation: "step-after",
|
|
@@ -1285,6 +1483,8 @@ function resolveStudySpecs(
|
|
|
1285
1483
|
anchors?: ReadonlyMap<string, StudyAnchor>,
|
|
1286
1484
|
): StudyResolutionResult {
|
|
1287
1485
|
const byId = new Map(baseSeries.map((series) => [series.id, series]));
|
|
1486
|
+
// A formula never takes a colour a series on the chart has: a crack in euros is not drawn in its EURUSD leg's yellow.
|
|
1487
|
+
const usedColors = new Set(baseSeries.map((series) => series.color.toLowerCase()));
|
|
1288
1488
|
const resolved: ResolvedSeries[] = [];
|
|
1289
1489
|
const warnings: string[] = [];
|
|
1290
1490
|
const errors: string[] = [];
|
|
@@ -1307,17 +1507,19 @@ function resolveStudySpecs(
|
|
|
1307
1507
|
return;
|
|
1308
1508
|
}
|
|
1309
1509
|
}
|
|
1310
|
-
let terms: FormulaTerms | null = null;
|
|
1510
|
+
let formula: { terms: FormulaTerms; unit: SeriesUnit } | null = null;
|
|
1311
1511
|
if (spec.kind === "formula") {
|
|
1312
|
-
const
|
|
1313
|
-
const
|
|
1314
|
-
if (
|
|
1315
|
-
errors.push(`${studyMessageName(spec)}: ${
|
|
1512
|
+
const terms = formulaTerms(spec, inputs as ResolvedSeries[]);
|
|
1513
|
+
const unit = "error" in terms ? terms : formulaUnit(terms, inputs as ResolvedSeries[]);
|
|
1514
|
+
if ("error" in unit) {
|
|
1515
|
+
errors.push(`${studyMessageName(spec)}: ${unit.error}`);
|
|
1316
1516
|
return;
|
|
1317
1517
|
}
|
|
1318
|
-
|
|
1518
|
+
formula = { terms: terms as FormulaTerms, unit };
|
|
1319
1519
|
}
|
|
1320
|
-
const color = spec.color ?? KIND_COLORS[spec.kind]
|
|
1520
|
+
const color = spec.color ?? KIND_COLORS[spec.kind]
|
|
1521
|
+
?? (isLevelFormula(spec.kind) ? freeStudyColor(index, usedColors) : STUDY_COLORS[index % STUDY_COLORS.length]!);
|
|
1522
|
+
if (isLevelFormula(spec.kind)) usedColors.add(color.toLowerCase());
|
|
1321
1523
|
if (spec.kind === "leverage" || spec.kind === "leverage-decay") {
|
|
1322
1524
|
const index = inputs.at(-1)!;
|
|
1323
1525
|
if (!isLeverageFactor(spec.parameters.factor)) {
|
|
@@ -1388,7 +1590,7 @@ function resolveStudySpecs(
|
|
|
1388
1590
|
resolved.push(...outputs);
|
|
1389
1591
|
return;
|
|
1390
1592
|
}
|
|
1391
|
-
else if (spec.kind === "formula" &&
|
|
1593
|
+
else if (spec.kind === "formula" && formula) outputs = resolveFormula(spec, inputs as ResolvedSeries[], formula.terms, formula.unit, color);
|
|
1392
1594
|
else if (spec.kind === "volume") {
|
|
1393
1595
|
outputs = resolveVolume(spec, input, color, nameVolumeInput);
|
|
1394
1596
|
if (!input.volumeUnit && outputs.some((output) => output.points.length > 0)) {
|
|
@@ -1402,10 +1604,12 @@ function resolveStudySpecs(
|
|
|
1402
1604
|
const differentCurrencies = inputCurrency !== null
|
|
1403
1605
|
&& pairedCurrency !== null
|
|
1404
1606
|
&& inputCurrency !== pairedCurrency;
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
);
|
|
1607
|
+
// Over an FX rate a price is converted, leaving one currency: EUR / barrel.
|
|
1608
|
+
const ratio = spec.kind === "ratio" ? ratioUnit(input, pairedInput) : null;
|
|
1609
|
+
if (ratio && differentCurrencies && unitCurrencies(ratio.unit).size !== 1) {
|
|
1610
|
+
const issue = fxConversionIssue(input, pairedInput, -1, ratio, legName(input));
|
|
1611
|
+
warnings.push(`${studyMessageName(spec)}: ${issue
|
|
1612
|
+
?? `${spec.kind} inputs use different currencies (${inputCurrency} and ${pairedCurrency}); raw values are not FX-converted.`}`);
|
|
1409
1613
|
}
|
|
1410
1614
|
if (spec.kind === "correlation" && input.nativeFrequency !== pairedInput.nativeFrequency) {
|
|
1411
1615
|
warnings.push(`${studyMessageName(spec)}: correlation mixes ${input.nativeFrequency} and ${pairedInput.nativeFrequency} observations; only matching observation times contribute.`);
|