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
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
seriesPriceReference,
|
|
14
14
|
} from "./format";
|
|
15
15
|
import type { CompositeChartProps, CompositeChartScene } from "./types";
|
|
16
|
-
import { seriesCueCssBackground, type SeriesLineCue } from "./series-cues";
|
|
16
|
+
import { DIRECTION_MARKER, seriesColorsDirection, seriesCueCssBackground, type SeriesLineCue } from "./series-cues";
|
|
17
17
|
import { DisclosureMarker } from "../../ui/disclosure-marker";
|
|
18
18
|
|
|
19
19
|
const LEGEND_WHEEL_DELTA_PER_CELL = 8;
|
|
@@ -120,12 +120,35 @@ function packLegendLines(widths: readonly number[], available: number, maxLines
|
|
|
120
120
|
return lines;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
/**
|
|
124
|
-
|
|
123
|
+
/** A triangle pointing up or down, as the desktop legend keys a bar's direction. */
|
|
124
|
+
const DIRECTION_TRIANGLES = {
|
|
125
|
+
up: "polygon(50% 0, 100% 100%, 0 100%)",
|
|
126
|
+
down: "polygon(0 0, 100% 0, 50% 100%)",
|
|
127
|
+
} as const;
|
|
128
|
+
|
|
129
|
+
/** The legend's marker for a series on the desktop: its line as the plot strokes it, its up and down colors, or a dot. */
|
|
130
|
+
function CompositeSeriesSwatch({ series, cue, downColor = null, filled = true }: {
|
|
125
131
|
series: ResolvedSeries;
|
|
126
132
|
cue: SeriesLineCue | null;
|
|
133
|
+
/** The down color, for a series keyed by direction: an up triangle in its color, then a down one in this. */
|
|
134
|
+
downColor?: string | null;
|
|
127
135
|
filled?: boolean;
|
|
128
136
|
}) {
|
|
137
|
+
if (downColor) {
|
|
138
|
+
return (
|
|
139
|
+
<Box
|
|
140
|
+
flexShrink={0}
|
|
141
|
+
flexDirection="row"
|
|
142
|
+
style={{ gap: 2, marginInlineEnd: 6, opacity: filled ? 1 : 0.35 }}
|
|
143
|
+
data-gloom-role="composite-chart-legend-marker"
|
|
144
|
+
data-gloom-cue="direction"
|
|
145
|
+
>
|
|
146
|
+
{([["up", series.color], ["down", downColor]] as const).map(([direction, color]) => (
|
|
147
|
+
<Box key={direction} flexShrink={0} style={{ width: 7, height: 7, backgroundColor: color, clipPath: DIRECTION_TRIANGLES[direction] }} />
|
|
148
|
+
))}
|
|
149
|
+
</Box>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
129
152
|
return cue ? (
|
|
130
153
|
// The line as the plot strokes it: solid, dashed, dotted or heavy.
|
|
131
154
|
<Box
|
|
@@ -162,6 +185,7 @@ export function CompositeLegend({
|
|
|
162
185
|
visibleSeriesIds,
|
|
163
186
|
lineCues = NO_LINE_CUES,
|
|
164
187
|
cellPlot = false,
|
|
188
|
+
directionDownColor = null,
|
|
165
189
|
width,
|
|
166
190
|
accessory,
|
|
167
191
|
accessoryWidth,
|
|
@@ -183,6 +207,12 @@ export function CompositeLegend({
|
|
|
183
207
|
lineCues?: ReadonlyMap<string, SeriesLineCue>;
|
|
184
208
|
/** The plot is cell text, so a line's cue is its mark rather than its dash. */
|
|
185
209
|
cellPlot?: boolean;
|
|
210
|
+
/**
|
|
211
|
+
* The color bars that closed down are drawn in, where the theme's up and
|
|
212
|
+
* down are not the familiar green and red: a candle series' marker then keys
|
|
213
|
+
* both (`▲▼`). Null keeps its dot.
|
|
214
|
+
*/
|
|
215
|
+
directionDownColor?: string | null;
|
|
186
216
|
width: number;
|
|
187
217
|
accessory: CompositeChartProps["legendAccessory"];
|
|
188
218
|
accessoryWidth: CompositeChartProps["legendAccessoryWidth"];
|
|
@@ -212,8 +242,9 @@ export function CompositeLegend({
|
|
|
212
242
|
const valueDates = legendValueDates(series, scene, cursorValueById);
|
|
213
243
|
const buildEntries = (withFiscalPeriods: boolean) => series.map((entry) => {
|
|
214
244
|
const cue = lineCues.get(entry.id) ?? null;
|
|
215
|
-
const
|
|
216
|
-
const
|
|
245
|
+
const downColor = !cue && directionDownColor && seriesColorsDirection(entry) ? directionDownColor : null;
|
|
246
|
+
const marker = cue ? `${cellPlot ? cue.glyph : cue.sample} ` : downColor ? DIRECTION_MARKER.text : DOT_MARKER;
|
|
247
|
+
const markerWidth = isDesktopWeb ? cue || downColor ? DOM_LINE_CELLS : DOM_DOT_CELLS : displayWidth(marker);
|
|
217
248
|
const toggleable = !!onToggleSeries && (isSeriesToggleable?.(entry) ?? true);
|
|
218
249
|
const cursorValue = cursorValueById.get(entry.id);
|
|
219
250
|
// A stacked layer reads its own amount, not the top of the stack it is drawn to.
|
|
@@ -253,6 +284,7 @@ export function CompositeLegend({
|
|
|
253
284
|
return {
|
|
254
285
|
entry,
|
|
255
286
|
cue,
|
|
287
|
+
downColor,
|
|
256
288
|
marker,
|
|
257
289
|
markerWidth,
|
|
258
290
|
label,
|
|
@@ -414,7 +446,7 @@ export function CompositeLegend({
|
|
|
414
446
|
scrollBox.scrollTo({ x: nextLeft, y: scrollBox.scrollTop });
|
|
415
447
|
};
|
|
416
448
|
const renderEntry = (
|
|
417
|
-
{ entry, cue, marker, text, statedUnit, valueTail, toggleable, tooltip, carried, width: entryWidth }: LegendEntry,
|
|
449
|
+
{ entry, cue, downColor, marker, text, statedUnit, valueTail, toggleable, tooltip, carried, width: entryWidth }: LegendEntry,
|
|
418
450
|
index: number,
|
|
419
451
|
) => {
|
|
420
452
|
const entryVisible = visibleSeriesIds.has(entry.id);
|
|
@@ -446,7 +478,13 @@ export function CompositeLegend({
|
|
|
446
478
|
title={isDesktopWeb ? tooltip : undefined}
|
|
447
479
|
>
|
|
448
480
|
{isDesktopWeb ? (
|
|
449
|
-
<CompositeSeriesSwatch series={entry} cue={cue} filled={entryVisible} />
|
|
481
|
+
<CompositeSeriesSwatch series={entry} cue={cue} downColor={downColor} filled={entryVisible} />
|
|
482
|
+
) : downColor && entryVisible ? (
|
|
483
|
+
<Text>
|
|
484
|
+
<Span fg={entry.color}>{DIRECTION_MARKER.up}</Span>
|
|
485
|
+
<Span fg={downColor}>{DIRECTION_MARKER.down}</Span>
|
|
486
|
+
{" "}
|
|
487
|
+
</Text>
|
|
450
488
|
) : (
|
|
451
489
|
<Text fg={entryVisible ? entry.color : themeColors.textMuted}>{marker}</Text>
|
|
452
490
|
)}
|
|
@@ -23,6 +23,8 @@ export interface CompositeChartLevel {
|
|
|
23
23
|
label?: string;
|
|
24
24
|
/** Exact instrument price, independent of the grid's compact tick formatter. */
|
|
25
25
|
axisLabel?: string;
|
|
26
|
+
/** A named reference guide keeps its axis label ahead of ordinary grid ticks. */
|
|
27
|
+
prioritizeAxisLabel?: boolean;
|
|
26
28
|
dashed?: boolean;
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -64,6 +64,39 @@ export function startPanGesture(
|
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/** Where the pointer was during a pan, for the speed it lets go at. */
|
|
68
|
+
export interface PanSample {
|
|
69
|
+
at: number;
|
|
70
|
+
x: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Only the last moments of a drag count: a pointer that stopped before letting go throws nothing. */
|
|
74
|
+
const PAN_RELEASE_WINDOW_MS = 100;
|
|
75
|
+
/** How long a throw takes to lose about two thirds of its speed. */
|
|
76
|
+
export const PAN_COAST_DECAY_MS = 325;
|
|
77
|
+
/**
|
|
78
|
+
* Plot widths per second a release must reach to coast, and below which a
|
|
79
|
+
* coast stops. Only a flick coasts: a drag placing a bar under the pointer
|
|
80
|
+
* moves at about one plot width a second and must leave it there.
|
|
81
|
+
*/
|
|
82
|
+
export const PAN_COAST_START_SPEED = 2.5;
|
|
83
|
+
export const PAN_COAST_STOP_SPEED = 0.02;
|
|
84
|
+
|
|
85
|
+
/** Cells per millisecond the pointer moved over its last samples, 0 when it had come to rest. */
|
|
86
|
+
export function panReleaseVelocity(samples: readonly PanSample[], now: number): number {
|
|
87
|
+
const recent = samples.filter((sample) => now - sample.at <= PAN_RELEASE_WINDOW_MS);
|
|
88
|
+
const first = recent[0];
|
|
89
|
+
const last = recent.at(-1);
|
|
90
|
+
if (!first || !last || last.at <= first.at) return 0;
|
|
91
|
+
return (last.x - first.x) / (last.at - first.at);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** One frame of a coast: the cells it moves (the decaying speed integrated over the frame) and the speed left. */
|
|
95
|
+
export function panCoastStep(velocity: number, elapsedMs: number): { cells: number; velocity: number } {
|
|
96
|
+
const decay = Math.exp(-Math.max(0, elapsedMs) / PAN_COAST_DECAY_MS);
|
|
97
|
+
return { cells: velocity * PAN_COAST_DECAY_MS * (1 - decay), velocity: velocity * decay };
|
|
98
|
+
}
|
|
99
|
+
|
|
67
100
|
export interface PendingWheel {
|
|
68
101
|
/** Fraction of the visible span, positive toward older observations. */
|
|
69
102
|
panRatio: number;
|
|
@@ -30,6 +30,7 @@ function sameSeriesMeta(left: ResolvedSeries, right: ResolvedSeries): boolean {
|
|
|
30
30
|
&& left.unitGroup === right.unitGroup
|
|
31
31
|
&& left.statedUnit === right.statedUnit
|
|
32
32
|
&& left.priceAssetCategory === right.priceAssetCategory
|
|
33
|
+
&& left.valueDecimals === right.valueDecimals
|
|
33
34
|
&& left.nativeFrequency === right.nativeFrequency
|
|
34
35
|
&& left.timestampMode === right.timestampMode
|
|
35
36
|
&& left.dataShape === right.dataShape
|
|
@@ -69,9 +69,14 @@ import {
|
|
|
69
69
|
COMPOSITE_PANEL_ROLE,
|
|
70
70
|
isVerticalWheelDirection,
|
|
71
71
|
manualValueAxes,
|
|
72
|
+
PAN_COAST_START_SPEED,
|
|
73
|
+
PAN_COAST_STOP_SPEED,
|
|
74
|
+
panCoastStep,
|
|
75
|
+
panReleaseVelocity,
|
|
72
76
|
startPanGesture,
|
|
73
77
|
releaseEditableFocus,
|
|
74
78
|
type PanGesture,
|
|
79
|
+
type PanSample,
|
|
75
80
|
type PendingAxisWheel,
|
|
76
81
|
type PendingWheel,
|
|
77
82
|
type ValueAxisStart,
|
|
@@ -542,6 +547,7 @@ export function CompositePanelSurface({
|
|
|
542
547
|
yRatio,
|
|
543
548
|
label: level.axisLabel ?? (formatAxisValue ? formatAxisValue(level.value, levelDomain) : formatCompositeCursorValue(level.value, levelDomain)),
|
|
544
549
|
color: level.color,
|
|
550
|
+
prioritizeAxisLabel: level.prioritizeAxisLabel,
|
|
545
551
|
})) : [], [formatAxisValue, levelDomain, projectedLevels, selectedLevelId]);
|
|
546
552
|
const buildAxisMarkers = (side: "left" | "right") => {
|
|
547
553
|
if (!panel.axes[side]) return undefined;
|
|
@@ -557,7 +563,8 @@ export function CompositePanelSurface({
|
|
|
557
563
|
);
|
|
558
564
|
const fixed = [lastPriceMarker, axisMarkers].filter(onSide).map(toAxisMarker);
|
|
559
565
|
// The prior close's label yields to every level's.
|
|
560
|
-
const
|
|
566
|
+
const referenceLabels = levelMarkers.filter((marker) => marker.prioritizeAxisLabel && onSide(marker)).map(toAxisMarker);
|
|
567
|
+
const levelLabels = [...levelMarkers.filter((marker) => !marker.prioritizeAxisLabel), priorCloseMarker].filter(onSide).map(toAxisMarker);
|
|
561
568
|
// A label is a row tall, so close levels would stack into an unreadable
|
|
562
569
|
// pile and hide the last price: each keeps its line, and only the labels
|
|
563
570
|
// with a row to themselves show. Desktop badges sit between rows.
|
|
@@ -567,8 +574,13 @@ export function CompositePanelSurface({
|
|
|
567
574
|
pixelY: tick.topPercent / 100 * Math.max(panel.height * cellHeightPx - 1, 1),
|
|
568
575
|
}))
|
|
569
576
|
: [...(side === "left" ? leftAxisLabels : rightAxisLabels)].map(([row, label]) => ({ id: undefined, row, pixelY: row * cellHeightPx, label, color: colors.textDim }));
|
|
577
|
+
const rowOf = (marker: ReturnType<typeof toAxisMarker>) => fractionalViewport ? marker.pixelY / cellHeightPx : marker.row;
|
|
578
|
+
// Named guides are part of the chart's readout: ordinary ticks yield to
|
|
579
|
+
// them, while a live price or cursor still keeps its place.
|
|
580
|
+
const references = fitAxisLabels(referenceLabels, fixed, rowOf);
|
|
570
581
|
const markers = [
|
|
571
|
-
...
|
|
582
|
+
...references,
|
|
583
|
+
...fitAxisLabels(levelLabels, [...references, ...fixed, ...grid], rowOf),
|
|
572
584
|
...fixed,
|
|
573
585
|
];
|
|
574
586
|
return markers.length > 0 ? markers : undefined;
|
|
@@ -726,6 +738,7 @@ export function CompositePanelSurface({
|
|
|
726
738
|
}
|
|
727
739
|
if (!updateCursor(event)) return;
|
|
728
740
|
const values = manualValueAxes(panel);
|
|
741
|
+
panSamplesRef.current = [{ at: performance.now(), x: getGlobalMouseX(event, renderer) }];
|
|
729
742
|
dragRef.current = {
|
|
730
743
|
...startPanGesture(
|
|
731
744
|
frame,
|
|
@@ -808,6 +821,8 @@ export function CompositePanelSurface({
|
|
|
808
821
|
return;
|
|
809
822
|
}
|
|
810
823
|
const globalX = getGlobalMouseX(event, renderer);
|
|
824
|
+
panSamplesRef.current.push({ at: performance.now(), x: globalX });
|
|
825
|
+
if (panSamplesRef.current.length > 12) panSamplesRef.current.shift();
|
|
811
826
|
if (drag.frame !== frame) {
|
|
812
827
|
// Data refreshed mid-drag. The new frame maps positions differently, so
|
|
813
828
|
// continue from where the plot is now instead of replaying the drag.
|
|
@@ -840,6 +855,43 @@ export function CompositePanelSurface({
|
|
|
840
855
|
updateCursor,
|
|
841
856
|
viewport,
|
|
842
857
|
]);
|
|
858
|
+
// A pan let go while still moving keeps going and slows down, on the
|
|
859
|
+
// desktop and web only: in a terminal every frame would upload the plot again.
|
|
860
|
+
const panSamplesRef = useRef<PanSample[]>([]);
|
|
861
|
+
const coastFrameRef = useRef<number | null>(null);
|
|
862
|
+
const coastPanRef = useRef(onPanViewport);
|
|
863
|
+
coastPanRef.current = onPanViewport;
|
|
864
|
+
const stopCoast = useCallback(() => {
|
|
865
|
+
if (coastFrameRef.current === null) return;
|
|
866
|
+
webFrame.cancelAnimationFrame?.(coastFrameRef.current);
|
|
867
|
+
coastFrameRef.current = null;
|
|
868
|
+
globalThis.removeEventListener?.("pointerdown", stopCoast, true);
|
|
869
|
+
globalThis.removeEventListener?.("keydown", stopCoast, true);
|
|
870
|
+
}, []);
|
|
871
|
+
const startCoast = useCallback((velocity: number, positionsPerCell: number) => {
|
|
872
|
+
stopCoast();
|
|
873
|
+
const requestFrame = webFrame.requestAnimationFrame;
|
|
874
|
+
const widthCells = Math.max(plotSize.width, 1);
|
|
875
|
+
const plotWidthsPerSecond = (speed: number) => Math.abs(speed) * 1000 / widthCells;
|
|
876
|
+
if (!isDesktopWeb || !requestFrame || plotWidthsPerSecond(velocity) < PAN_COAST_START_SPEED) return;
|
|
877
|
+
if (globalThis.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return;
|
|
878
|
+
let speed = velocity;
|
|
879
|
+
let last = performance.now();
|
|
880
|
+
const step = () => {
|
|
881
|
+
const now = performance.now();
|
|
882
|
+
const next = panCoastStep(speed, now - last);
|
|
883
|
+
last = now;
|
|
884
|
+
speed = next.velocity;
|
|
885
|
+
coastPanRef.current(next.cells * positionsPerCell);
|
|
886
|
+
if (plotWidthsPerSecond(speed) < PAN_COAST_STOP_SPEED) stopCoast();
|
|
887
|
+
else coastFrameRef.current = requestFrame(step);
|
|
888
|
+
};
|
|
889
|
+
coastFrameRef.current = requestFrame(step);
|
|
890
|
+
// Any press or key anywhere, a range button or a reset, takes the view back from the coast.
|
|
891
|
+
globalThis.addEventListener?.("pointerdown", stopCoast, true);
|
|
892
|
+
globalThis.addEventListener?.("keydown", stopCoast, true);
|
|
893
|
+
}, [isDesktopWeb, plotSize.width, stopCoast]);
|
|
894
|
+
useEffect(() => stopCoast, [stopCoast]);
|
|
843
895
|
const finishToolDrag = useCallback(() => {
|
|
844
896
|
const drag = dragRef.current;
|
|
845
897
|
if (!drag || drag.kind === "pan") return;
|
|
@@ -862,12 +914,14 @@ export function CompositePanelSurface({
|
|
|
862
914
|
if (range) onSetViewport(range);
|
|
863
915
|
}, [drawColor, frame.minimumSpanMs, levels, onDraw, onSetViewport, panel, scene]);
|
|
864
916
|
const resetDrag = useCallback(() => {
|
|
865
|
-
|
|
917
|
+
const drag = dragRef.current;
|
|
918
|
+
if (drag?.kind === "pan") {
|
|
866
919
|
dragRef.current = null;
|
|
920
|
+
startCoast(panReleaseVelocity(panSamplesRef.current, performance.now()), drag.positionsPerCell);
|
|
867
921
|
return;
|
|
868
922
|
}
|
|
869
923
|
finishToolDrag();
|
|
870
|
-
}, [finishToolDrag]);
|
|
924
|
+
}, [finishToolDrag, startCoast]);
|
|
871
925
|
// Said once per change, so a pointer move costs the chart nothing.
|
|
872
926
|
const reportedDragRef = useRef(false);
|
|
873
927
|
const reportDrag = useCallback((dragging: boolean) => {
|
|
@@ -876,10 +930,11 @@ export function CompositePanelSurface({
|
|
|
876
930
|
onDragActiveChange?.(dragging);
|
|
877
931
|
}, [onDragActiveChange]);
|
|
878
932
|
const pressPlot = useCallback((event: ChartMouseEvent) => {
|
|
933
|
+
stopCoast();
|
|
879
934
|
const before = dragRef.current;
|
|
880
935
|
startDrag(event);
|
|
881
936
|
if (dragRef.current !== null && dragRef.current !== before) reportDrag(true);
|
|
882
|
-
}, [reportDrag, startDrag]);
|
|
937
|
+
}, [reportDrag, startDrag, stopCoast]);
|
|
883
938
|
const releasePlot = useCallback(() => {
|
|
884
939
|
resetDrag();
|
|
885
940
|
reportDrag(false);
|
|
@@ -922,6 +977,7 @@ export function CompositePanelSurface({
|
|
|
922
977
|
const panFromWheel = useCallback((event: ChartMouseEvent) => {
|
|
923
978
|
const scroll = event.scroll;
|
|
924
979
|
if (!scroll) return;
|
|
980
|
+
stopCoast();
|
|
925
981
|
onActivate?.();
|
|
926
982
|
consumeChartMouseEvent(event);
|
|
927
983
|
const pointer = plotPointer(event);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ResolvedSeries, TimeSeriesPoint } from "../../../time-series/types";
|
|
2
|
+
import { formatCompositeSeriesValue } from "./format";
|
|
3
|
+
import type { CompositeViewportRange } from "./interactions";
|
|
4
|
+
import type { CompositeChartLevel, CompositeChartLevels } from "./levels";
|
|
5
|
+
|
|
6
|
+
/** Population statistics of the finite observations actually in the window. */
|
|
7
|
+
export function compositeRangeStatistics(points: readonly TimeSeriesPoint[], viewport: CompositeViewportRange) {
|
|
8
|
+
const start = viewport.start.getTime();
|
|
9
|
+
const end = viewport.end.getTime();
|
|
10
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || start > end) return null;
|
|
11
|
+
let count = 0;
|
|
12
|
+
let mean = 0;
|
|
13
|
+
let squaredDeviations = 0;
|
|
14
|
+
for (const point of points) {
|
|
15
|
+
const time = point.date.getTime();
|
|
16
|
+
const value = Number.isFinite(point.value) ? point.value : point.close;
|
|
17
|
+
if (!Number.isFinite(time) || time < start || time > end
|
|
18
|
+
|| typeof value !== "number" || !Number.isFinite(value)) continue;
|
|
19
|
+
count += 1;
|
|
20
|
+
// Welford's update avoids subtracting nearly equal squared sums when
|
|
21
|
+
// valuations are tightly clustered around a large level.
|
|
22
|
+
const delta = value - mean;
|
|
23
|
+
mean += delta / count;
|
|
24
|
+
squaredDeviations += delta * (value - mean);
|
|
25
|
+
}
|
|
26
|
+
if (!count || !Number.isFinite(mean) || !Number.isFinite(squaredDeviations)) return null;
|
|
27
|
+
return { count, mean, standardDeviation: Math.sqrt(Math.max(0, squaredDeviations / count)) };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Read-only levels share the chart's axis labels, vector, Kitty and text paths. */
|
|
31
|
+
export function compositeRangeStatisticsLevels(
|
|
32
|
+
series: ResolvedSeries | undefined,
|
|
33
|
+
viewport: CompositeViewportRange | null,
|
|
34
|
+
color: string,
|
|
35
|
+
formatValue: (value: number, series: ResolvedSeries) => string = formatCompositeSeriesValue,
|
|
36
|
+
): CompositeChartLevels | null {
|
|
37
|
+
if (!series || series.hidden || !viewport) return null;
|
|
38
|
+
const statistics = compositeRangeStatistics(series.points, viewport);
|
|
39
|
+
if (!statistics || statistics.count < 20) return null;
|
|
40
|
+
const { mean, standardDeviation } = statistics;
|
|
41
|
+
const guides = [
|
|
42
|
+
{ id: "mean", label: "Avg", value: mean, dashed: false },
|
|
43
|
+
{ id: "upper", label: "+1σ", value: mean + standardDeviation, dashed: true },
|
|
44
|
+
{ id: "lower", label: "−1σ", value: mean - standardDeviation, dashed: true },
|
|
45
|
+
];
|
|
46
|
+
const items: CompositeChartLevel[] = [];
|
|
47
|
+
for (const guide of guides) {
|
|
48
|
+
// A constant window (including one observation) has only its mean.
|
|
49
|
+
if (!Number.isFinite(guide.value) || items.some((item) => item.value === guide.value)) continue;
|
|
50
|
+
items.push({
|
|
51
|
+
id: `range-statistics:${series.id}:${guide.id}`,
|
|
52
|
+
value: guide.value,
|
|
53
|
+
color,
|
|
54
|
+
dashed: guide.dashed,
|
|
55
|
+
axisLabel: `${guide.label} ${formatValue(guide.value, series)}`,
|
|
56
|
+
prioritizeAxisLabel: true,
|
|
57
|
+
editable: false,
|
|
58
|
+
actionable: false,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return { seriesId: series.id, items };
|
|
62
|
+
}
|
|
@@ -436,13 +436,14 @@ function drawColumns(
|
|
|
436
436
|
domain: CompositeAxisDomain,
|
|
437
437
|
color: RgbaColor,
|
|
438
438
|
negativeColor: RgbaColor,
|
|
439
|
+
fallColor: RgbaColor,
|
|
439
440
|
layout: CompositeColumnLayout,
|
|
440
441
|
widthByFamily: ReadonlyMap<string, number>,
|
|
441
442
|
opacity: number,
|
|
442
443
|
): void {
|
|
443
444
|
const baseline = pixelY(0, domain, height) ?? height - 1;
|
|
444
445
|
for (const projected of series.points) {
|
|
445
|
-
const fill = projected.value < 0 ? negativeColor : color;
|
|
446
|
+
const fill = projected.value < 0 ? negativeColor : projected.point.fell ? fallColor : color;
|
|
446
447
|
const point = pixelPoint(projected, width, height, domain.manual);
|
|
447
448
|
const geometry = columnPixelGeometry(projected, width, layout, widthByFamily);
|
|
448
449
|
fillRect(
|
|
@@ -487,20 +488,27 @@ function drawOhlc(
|
|
|
487
488
|
const closeY = pixelY(close, domain, height);
|
|
488
489
|
if (highY === null || lowY === null || closeY === null) continue;
|
|
489
490
|
const candleColor = close >= open ? color : negative;
|
|
490
|
-
|
|
491
|
-
if (
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
x
|
|
499
|
-
|
|
500
|
-
candleColor,
|
|
501
|
-
|
|
491
|
+
const style = series.source.style;
|
|
492
|
+
if (style !== "ohlc" && style !== "hlc" && openY !== null) {
|
|
493
|
+
const left = x - candleWidth / 2;
|
|
494
|
+
const right = x + candleWidth / 2;
|
|
495
|
+
const top = Math.min(openY, closeY);
|
|
496
|
+
const bottom = Math.max(openY, closeY) + 1;
|
|
497
|
+
// A hollow rising candle is an outline the wick stops at, as on other terminals.
|
|
498
|
+
if (style === "hollow" && close >= open) {
|
|
499
|
+
drawLine(data, width, height, x, highY, x, top, candleColor, 1.1);
|
|
500
|
+
drawLine(data, width, height, x, bottom, x, lowY, candleColor, 1.1);
|
|
501
|
+
drawLine(data, width, height, left, top, right, top, candleColor, 1.1);
|
|
502
|
+
drawLine(data, width, height, left, bottom, right, bottom, candleColor, 1.1);
|
|
503
|
+
drawLine(data, width, height, left, top, left, bottom, candleColor, 1.1);
|
|
504
|
+
drawLine(data, width, height, right, top, right, bottom, candleColor, 1.1);
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
drawLine(data, width, height, x, highY, x, lowY, candleColor, 1.1);
|
|
508
|
+
fillRect(data, width, height, left, top, right, bottom, candleColor);
|
|
502
509
|
continue;
|
|
503
510
|
}
|
|
511
|
+
drawLine(data, width, height, x, highY, x, lowY, candleColor, 1.1);
|
|
504
512
|
if (series.source.style === "ohlc" && openY !== null) {
|
|
505
513
|
drawLine(data, width, height, x - candleWidth / 2, openY, x, openY, candleColor, 1.2);
|
|
506
514
|
}
|
|
@@ -585,6 +593,7 @@ export function renderCompositePanelBitmap(
|
|
|
585
593
|
domain,
|
|
586
594
|
color,
|
|
587
595
|
negativeColor,
|
|
596
|
+
negative,
|
|
588
597
|
columnLayout,
|
|
589
598
|
columnWidthByFamily,
|
|
590
599
|
mixesColumnsWithOtherMarks ? 0.48 : 0.72,
|
|
@@ -615,6 +624,8 @@ export function renderCompositePanelBitmap(
|
|
|
615
624
|
break;
|
|
616
625
|
}
|
|
617
626
|
case "candles":
|
|
627
|
+
case "hollow":
|
|
628
|
+
case "heikin-ashi":
|
|
618
629
|
case "ohlc":
|
|
619
630
|
case "hlc":
|
|
620
631
|
drawOhlc(data, width, height, series, domain, color, negative);
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
ResolvedSeries,
|
|
5
5
|
TimeSeriesPoint,
|
|
6
6
|
} from "../../../time-series/types";
|
|
7
|
-
import { effectiveTimeSeriesPointTime, timeSeriesObservationKey } from "../../../time-series/alignment";
|
|
7
|
+
import { effectiveTimeSeriesPointTime, sortedUniquePoints, timeSeriesObservationKey } from "../../../time-series/alignment";
|
|
8
8
|
import type {
|
|
9
9
|
BuildCompositeChartSceneOptions,
|
|
10
10
|
CompositeAxisDomain,
|
|
@@ -36,6 +36,37 @@ function resolveTimeSeriesPointValue(point: TimeSeriesPoint): number | null {
|
|
|
36
36
|
return null;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Heikin Ashi bars in place of a series' own: each close averages the bar's
|
|
41
|
+
* four prices and each open sits halfway along the previous Heikin Ashi body,
|
|
42
|
+
* so the chain runs from the first loaded bar, before any viewport cut. The
|
|
43
|
+
* real close stays in `rawValue` for the last price marker.
|
|
44
|
+
*/
|
|
45
|
+
export function heikinAshiPoints(points: readonly TimeSeriesPoint[]): TimeSeriesPoint[] {
|
|
46
|
+
let previous: { open: number; close: number } | null = null;
|
|
47
|
+
// Chain over the bars the chart draws: one per observation, in time order,
|
|
48
|
+
// so a superseded copy of a bar never feeds the next open.
|
|
49
|
+
return sortedUniquePoints(points).map((point) => {
|
|
50
|
+
const close = point.close ?? point.value;
|
|
51
|
+
if (!isFiniteNumber(close)) return point;
|
|
52
|
+
const open = isFiniteNumber(point.open) ? point.open : close;
|
|
53
|
+
const high = isFiniteNumber(point.high) ? point.high : Math.max(open, close);
|
|
54
|
+
const low = isFiniteNumber(point.low) ? point.low : Math.min(open, close);
|
|
55
|
+
const haClose = (open + high + low + close) / 4;
|
|
56
|
+
const haOpen = previous ? (previous.open + previous.close) / 2 : (open + close) / 2;
|
|
57
|
+
previous = { open: haOpen, close: haClose };
|
|
58
|
+
return {
|
|
59
|
+
...point,
|
|
60
|
+
value: haClose,
|
|
61
|
+
rawValue: close,
|
|
62
|
+
open: haOpen,
|
|
63
|
+
high: Math.max(high, haOpen, haClose),
|
|
64
|
+
low: Math.min(low, haOpen, haClose),
|
|
65
|
+
close: haClose,
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
39
70
|
function pointTime(point: TimeSeriesPoint): number | null {
|
|
40
71
|
const time = point.date instanceof Date ? point.date.getTime() : new Date(point.date).getTime();
|
|
41
72
|
return Number.isFinite(time) ? time : null;
|
|
@@ -330,11 +361,15 @@ function buildAxisDomain(
|
|
|
330
361
|
scale: PanelScale,
|
|
331
362
|
rows: number,
|
|
332
363
|
manualScale?: CompositeManualValueScale,
|
|
364
|
+
referenceLevels?: BuildCompositeChartSceneOptions["referenceLevels"],
|
|
333
365
|
): CompositeAxisDomain | undefined {
|
|
334
366
|
const axisSeries = series.filter((entry) => entry.axis === side);
|
|
335
367
|
if (axisSeries.length === 0) return undefined;
|
|
336
368
|
const values = axisSeries.flatMap(seriesDomainValues);
|
|
337
|
-
const
|
|
369
|
+
const references = referenceLevels && axisSeries.some((entry) => entry.id === referenceLevels.seriesId)
|
|
370
|
+
? referenceLevels.items.map((level) => level.value).filter(Number.isFinite)
|
|
371
|
+
: [];
|
|
372
|
+
const fitted = paddedDomain([...values, ...priorCloseInReach(axisSeries, values), ...references], scale);
|
|
338
373
|
const scaleKey = valueScaleKey(scale, axisSeries);
|
|
339
374
|
// A range set by hand holds while the axis plots the same series and some
|
|
340
375
|
// of their data is in it; otherwise the axis fits the data again.
|
|
@@ -498,9 +533,11 @@ function primaryPriceSeries(series: readonly ResolvedSeries[]): ResolvedSeries |
|
|
|
498
533
|
)) ?? null;
|
|
499
534
|
}
|
|
500
535
|
|
|
501
|
-
function lastCloseOf(points: readonly CompositeProjectedPoint[]): number | null {
|
|
536
|
+
function lastCloseOf(points: readonly CompositeProjectedPoint[], heikinAshi = false): number | null {
|
|
502
537
|
const last = points.at(-1);
|
|
503
538
|
if (!last) return null;
|
|
539
|
+
// A Heikin Ashi close is an average; the marker names the price that traded.
|
|
540
|
+
if (heikinAshi && isFiniteNumber(last.point.rawValue)) return last.point.rawValue;
|
|
504
541
|
return isFiniteNumber(last.point.close) ? last.point.close : last.value;
|
|
505
542
|
}
|
|
506
543
|
|
|
@@ -518,7 +555,7 @@ function attachLastPriceMarker(
|
|
|
518
555
|
// A rejected or missing latest bar cannot make an older close current.
|
|
519
556
|
const latest = normalizedSourcePoints(primary).at(-1);
|
|
520
557
|
if (latest?.point.provenance?.priceHistoryIntegrity || latest?.value === null) return;
|
|
521
|
-
const value = lastCloseOf(projected.points);
|
|
558
|
+
const value = lastCloseOf(projected.points, primary.style === "heikin-ashi");
|
|
522
559
|
if (value === null) return;
|
|
523
560
|
const yRatio = projectCompositeValue(value, domain);
|
|
524
561
|
// An axis scaled by hand can leave the last close off the panel.
|
|
@@ -760,7 +797,9 @@ export function buildCompositeChartScene(
|
|
|
760
797
|
panels: ChartPanelSpec[],
|
|
761
798
|
options: BuildCompositeChartSceneOptions,
|
|
762
799
|
): CompositeChartScene | null {
|
|
763
|
-
const dataSeries = series
|
|
800
|
+
const dataSeries = series
|
|
801
|
+
.map((entry) => entry.style === "heikin-ashi" ? { ...entry, points: heikinAshiPoints(entry.points) } : entry)
|
|
802
|
+
.filter((entry) => normalizedPoints(entry).length > 0);
|
|
764
803
|
if (dataSeries.length === 0) return null;
|
|
765
804
|
|
|
766
805
|
// A known missing observation owns a date even though it has no drawable
|
|
@@ -849,8 +888,8 @@ export function buildCompositeChartScene(
|
|
|
849
888
|
? dataSeries.filter((entry) => entry.panelId === panel.id)
|
|
850
889
|
: panelSeries;
|
|
851
890
|
const rows = panelHeights.get(panel.id) ?? 1;
|
|
852
|
-
const left = buildAxisDomain("left", domainSeries, scale, rows, options.valueScales?.[valueScaleSlot(panel.id, "left")]);
|
|
853
|
-
const right = buildAxisDomain("right", domainSeries, scale, rows, options.valueScales?.[valueScaleSlot(panel.id, "right")]);
|
|
891
|
+
const left = buildAxisDomain("left", domainSeries, scale, rows, options.valueScales?.[valueScaleSlot(panel.id, "left")], options.referenceLevels);
|
|
892
|
+
const right = buildAxisDomain("right", domainSeries, scale, rows, options.valueScales?.[valueScaleSlot(panel.id, "right")], options.referenceLevels);
|
|
854
893
|
const axes: Partial<Record<CompositeAxisSide, CompositeAxisDomain>> = { left, right };
|
|
855
894
|
return {
|
|
856
895
|
id: panel.id,
|
|
@@ -7,20 +7,23 @@ import type { SeriesAxisTag } from "./panel-labels";
|
|
|
7
7
|
* ties. A forward pass separates neighbours; a backward pass fits the bottom
|
|
8
8
|
* edge. Panels shorter than the readout use parallel lanes, not dropped tags
|
|
9
9
|
* or smaller type. The gutter reserves those lanes before the cursor moves.
|
|
10
|
+
* Only a tag pushed off its value's height gets a leader: a tick beside a tag
|
|
11
|
+
* still on its value reads as a minus sign (`-8k` for a positive 8k).
|
|
10
12
|
*/
|
|
11
13
|
export function placeSeriesAxisTags(tags: readonly SeriesAxisTag[], pixelHeight: number, labelHeight: number) {
|
|
12
14
|
const height = Math.max(pixelHeight, labelHeight);
|
|
13
15
|
const capacity = Math.max(1, Math.floor(height / labelHeight));
|
|
14
16
|
const sorted = tags.map((tag, order) => ({ ...tag, order, anchorY: clamp(tag.yRatio, 0, 1) * Math.max(pixelHeight - 1, 0) }))
|
|
15
17
|
.sort((a, b) => a.anchorY - b.anchorY || a.order - b.order);
|
|
16
|
-
const placed: Array<SeriesAxisTag & { anchorY: number; centerY: number; lane: number }> = [];
|
|
18
|
+
const placed: Array<SeriesAxisTag & { anchorY: number; centerY: number; lane: number; leader: boolean }> = [];
|
|
17
19
|
for (let start = 0; start < sorted.length; start += capacity) {
|
|
18
20
|
const lane = sorted.slice(start, start + capacity);
|
|
19
21
|
const centers = lane.map((tag) => clamp(tag.anchorY, labelHeight / 2, height - labelHeight / 2));
|
|
20
22
|
for (let i = 1; i < centers.length; i++) centers[i] = Math.max(centers[i]!, centers[i - 1]! + labelHeight);
|
|
21
23
|
if (centers.length) centers[centers.length - 1] = Math.min(centers.at(-1)!, height - labelHeight / 2);
|
|
22
24
|
for (let i = centers.length - 2; i >= 0; i--) centers[i] = Math.min(centers[i]!, centers[i + 1]! - labelHeight);
|
|
23
|
-
lane.forEach((tag, index) => placed.push({ ...tag, centerY: centers[index]!, lane: Math.floor(start / capacity)
|
|
25
|
+
lane.forEach((tag, index) => placed.push({ ...tag, centerY: centers[index]!, lane: Math.floor(start / capacity),
|
|
26
|
+
leader: Math.abs(centers[index]! - tag.anchorY) > labelHeight / 2 }));
|
|
24
27
|
}
|
|
25
28
|
return placed;
|
|
26
29
|
}
|
|
@@ -37,13 +40,14 @@ export function SeriesAxisTags({ placed, pixelHeight, lanes, background }: {
|
|
|
37
40
|
const edge = left ? "right" : "left";
|
|
38
41
|
const inset = `${tag.lane * 100 / lanes}%`;
|
|
39
42
|
return <Box key={tag.seriesId} style={{ position: "absolute", inset: 0, pointerEvents: "none" }}>
|
|
40
|
-
{/* The
|
|
43
|
+
{/* The anchor stays at the observation; it draws a tick when the badge had to move off it. */}
|
|
41
44
|
<Box
|
|
42
45
|
data-gloom-role="chart-series-axis-anchor"
|
|
43
46
|
data-series-id={tag.seriesId}
|
|
44
|
-
|
|
47
|
+
data-leader={tag.leader ? "true" : undefined}
|
|
48
|
+
style={{ position: "absolute", [edge]: 0, top: percent(tag.anchorY), width: `calc(${inset} + .5em)`, height: 0, borderTop: tag.leader ? `1px ${tag.carried ? "dashed" : "solid"} ${tag.color}` : undefined }}
|
|
45
49
|
/>
|
|
46
|
-
<Box style={{ position: "absolute", [edge]: `calc(${inset} + .25em)`, top: percent(Math.min(tag.anchorY, tag.centerY)), height: percent(Math.abs(tag.centerY - tag.anchorY)), width: 0, borderLeft: `1px solid ${tag.color}` }} />
|
|
50
|
+
{tag.leader ? <Box style={{ position: "absolute", [edge]: `calc(${inset} + .25em)`, top: percent(Math.min(tag.anchorY, tag.centerY)), height: percent(Math.abs(tag.centerY - tag.anchorY)), width: 0, borderLeft: `1px solid ${tag.color}` }} /> : null}
|
|
47
51
|
<Box
|
|
48
52
|
data-gloom-role="chart-series-axis-tag"
|
|
49
53
|
data-series-id={tag.seriesId}
|
|
@@ -52,12 +56,15 @@ export function SeriesAxisTags({ placed, pixelHeight, lanes, background }: {
|
|
|
52
56
|
data-carried={tag.carried ? "true" : undefined}
|
|
53
57
|
aria-label={`${tag.seriesLabel}: ${tag.label}${tag.carried ? " (carried)" : ""}`}
|
|
54
58
|
style={{
|
|
55
|
-
position: "absolute", [edge]:
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
position: "absolute", [edge]: inset, top: percent(tag.centerY), transform: "translateY(-50%)",
|
|
60
|
+
// The value sits in a box in its series colour, as a chart level's label does, so nothing
|
|
61
|
+
// beside it can read as the value's sign. A box wider than its lane is clipped, not wrapped.
|
|
62
|
+
width: "max-content", maxWidth: `calc(${100 / lanes}% - ${lanes > 1 ? ".25em" : "0px"})`,
|
|
63
|
+
boxSizing: "border-box", height: "1.5em", lineHeight: "1.5em", paddingInline: ".15em",
|
|
64
|
+
alignItems: left ? "flex-end" : "flex-start", justifyContent: "center", overflow: "hidden",
|
|
58
65
|
// Every badge paints above every leader, including another lane's.
|
|
59
66
|
zIndex: 2, color: tag.color, backgroundColor: background, whiteSpace: "nowrap",
|
|
60
|
-
|
|
67
|
+
border: `1px ${tag.carried ? "dashed" : "solid"} ${tag.color}`,
|
|
61
68
|
}}
|
|
62
69
|
>
|
|
63
70
|
<Text fg={tag.color} selectable={false}>{tag.label}</Text>
|
|
@@ -69,3 +69,17 @@ export function seriesCueCssBackground(cue: SeriesLineCue, color: string): strin
|
|
|
69
69
|
});
|
|
70
70
|
return `repeating-linear-gradient(to right, ${stops.join(", ")})`;
|
|
71
71
|
}
|
|
72
|
+
|
|
73
|
+
/** The OHLC family: one series drawn in the up color for a bar that closed up and the down color for one that closed down. */
|
|
74
|
+
const DIRECTION_STYLES = new Set<SeriesStyle>(["candles", "ohlc", "hlc"]);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Whether a series colors each bar by its direction, so its legend entry can
|
|
78
|
+
* key the two colors (`▲▼`) where the theme's are not the familiar green and red.
|
|
79
|
+
*/
|
|
80
|
+
export function seriesColorsDirection(series: Pick<ResolvedSeries, "style">): boolean {
|
|
81
|
+
return DIRECTION_STYLES.has(series.style);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The legend marker that keys up and down: the up color's triangle, then the down color's. */
|
|
85
|
+
export const DIRECTION_MARKER = { up: "▲", down: "▼", text: "▲▼ " } as const;
|