gloomberb 0.19.1 → 0.19.3
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 +7 -1
- package/package.json +2 -1
- package/patches/electrobun@1.18.1.patch +108 -1
- package/src/account/upgrade-dialog.tsx +4 -23
- package/src/api-client/coalesce.ts +118 -0
- package/src/api-client/credit-boards.ts +13 -0
- package/src/api-client/crypto-etf-flows.ts +6 -0
- package/src/api-client/crypto-markets.ts +40 -0
- package/src/api-client/data.ts +201 -14
- package/src/api-client/equity-screener.ts +10 -0
- package/src/api-client/errors.ts +58 -0
- package/src/api-client/gpu.ts +29 -0
- package/src/api-client/index.ts +5 -2
- package/src/api-client/plan-access.ts +10 -1
- package/src/api-client/plan-rules.ts +35 -0
- package/src/api-client/research-activity.ts +1 -32
- package/src/api-client/types.ts +21 -1
- package/src/app/attention-notice.tsx +94 -0
- package/src/app/runtime/startup.ts +3 -1
- package/src/app.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +2 -1
- package/src/capabilities/chart-series.ts +2 -1
- package/src/capabilities/factories.ts +1 -0
- package/src/cli/ascii-output.ts +42 -0
- package/src/cli/base-converter.ts +10 -2
- package/src/cli/command-options.ts +16 -1
- package/src/cli/commands/crypto-hints.ts +23 -4
- package/src/cli/commands/market.ts +101 -27
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +14 -10
- package/src/cli/commands/search.ts +84 -25
- package/src/cli/commands/system.ts +22 -30
- package/src/cli/commands/ticker.ts +49 -9
- package/src/cli/commands/version.ts +33 -0
- package/src/cli/context.ts +6 -9
- package/src/cli/data-dir.ts +7 -0
- package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
- package/src/cli/desktop-pane-shot-browser.ts +320 -0
- package/src/cli/desktop-pane-shot-routes.ts +26 -1
- package/src/cli/desktop-pane-shot.ts +196 -256
- package/src/cli/early-command.ts +42 -0
- package/src/cli/earnings-calendar.ts +9 -0
- package/src/cli/entry.ts +19 -13
- package/src/cli/errors.ts +12 -2
- package/src/cli/glossary.ts +2 -1
- package/src/cli/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +189 -11
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +51 -5
- package/src/cli/listing-arg.ts +63 -10
- package/src/cli/options.ts +1 -1
- package/src/cli/output-file.ts +60 -13
- package/src/cli/pane-functions/catalog.ts +14 -3
- package/src/cli/pane-functions/data.ts +1 -1
- package/src/cli/pane-functions/dom.ts +45 -0
- package/src/cli/pane-functions/freshness.ts +46 -9
- package/src/cli/pane-functions/function-help.ts +17 -9
- package/src/cli/pane-functions/headless.ts +22 -14
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +6 -2
- package/src/cli/pane-functions/resolver.ts +27 -5
- package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
- package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
- package/src/cli/pane-functions/screenshot-http.ts +70 -0
- package/src/cli/pane-functions/screenshot-render.ts +19 -5
- package/src/cli/quote-answer.ts +49 -6
- package/src/cli/registry.ts +41 -13
- package/src/cli/result.ts +12 -6
- package/src/cli/ticker-resolution.ts +2 -1
- package/src/cli/types.ts +2 -0
- package/src/components/chart/composite/format.ts +3 -0
- package/src/components/chart-table/header.tsx +1 -1
- package/src/components/chart-table/layout.ts +28 -11
- package/src/components/command-bar/action-runtime.ts +4 -0
- package/src/components/command-bar/commands/direct/index.ts +18 -33
- package/src/components/command-bar/commands/direct/runtime.ts +5 -0
- package/src/components/command-bar/commands/registry.ts +10 -1
- package/src/components/command-bar/routes/root/command-items.ts +9 -4
- package/src/components/command-bar/routes/root/runtime.ts +51 -6
- package/src/components/command-bar/selection-runtime.ts +12 -29
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/header.tsx +10 -5
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +8 -2
- package/src/components/layout/pane/footer/model.ts +73 -6
- package/src/components/layout/pane/header.tsx +2 -2
- package/src/components/layout/pane/status-footer.ts +2 -1
- package/src/components/market-board/index.tsx +3 -2
- package/src/components/onboarding/desks-step.tsx +12 -4
- package/src/components/onboarding/first-run-workspace.ts +81 -4
- package/src/components/onboarding/onboarding-frame.tsx +4 -2
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +60 -26
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
- package/src/components/onboarding/run-setup-again.ts +24 -0
- package/src/components/onboarding/use-onboarding-progress.ts +33 -15
- package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
- package/src/components/onboarding/wizard-model.ts +57 -3
- package/src/components/onboarding/wizard-positions.ts +1 -1
- package/src/components/ui/delayed-mark.tsx +56 -0
- package/src/components/ui/display.tsx +16 -5
- package/src/components/ui/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +16 -3
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/core/app-services.ts +11 -2
- package/src/data/app-persistence.ts +9 -7
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/plugin-cache.ts +15 -0
- package/src/data/plugin-state-store.ts +81 -24
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +165 -90
- package/src/data/session-store.ts +31 -8
- package/src/data/sqlite/access.ts +324 -0
- package/src/data/sqlite/checkpointer.ts +285 -0
- package/src/data/sqlite/database.ts +80 -4
- package/src/data/sqlite/retry.ts +50 -9
- package/src/data/ticker-store.ts +57 -23
- package/src/docs/snapshot.json +191 -78
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -2
- package/src/layout/desk-keys.ts +4 -1
- package/src/layout/desks.ts +38 -5
- package/src/layout-marketplace/gallery-desktop.tsx +45 -16
- package/src/market-data/batched-reads.ts +16 -0
- package/src/market-data/coordinator/auxiliary.ts +4 -2
- package/src/market-data/coordinator/index.ts +11 -2
- package/src/market-data/futures-board.ts +1 -1
- package/src/market-data/futures-contract.ts +3 -1
- package/src/market-data/market/index-level.ts +6 -4
- package/src/market-data/published-apac-sessions.ts +6 -0
- package/src/market-data/realized-volatility.ts +4 -2
- package/src/news/aggregator.ts +7 -4
- package/src/news/news-model.ts +11 -0
- package/src/news/story-id.ts +22 -0
- package/src/news/ticker-symbols.ts +17 -1
- package/src/notifications/app-notifier.ts +35 -14
- package/src/plugins/auto-update-node.ts +10 -6
- package/src/plugins/builtin/account-management/form-components.tsx +1 -1
- package/src/plugins/builtin/account-management/index.tsx +2 -3
- package/src/plugins/builtin/account-management/model.ts +1 -1
- package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
- package/src/plugins/builtin/alerts/events.ts +10 -2
- package/src/plugins/builtin/alerts/index.tsx +5 -5
- package/src/plugins/builtin/alerts/research-builder.ts +5 -1
- package/src/plugins/builtin/alerts/research-rules.ts +18 -0
- package/src/plugins/builtin/alerts/targets.ts +4 -1
- package/src/plugins/builtin/attention/headless.ts +1 -1
- package/src/plugins/builtin/attention/index.tsx +2 -4
- package/src/plugins/builtin/attention/listing.ts +1 -1
- package/src/plugins/builtin/attention/series.ts +1 -1
- package/src/plugins/builtin/awards/index.tsx +1 -2
- package/src/plugins/builtin/backtest/client.ts +1 -0
- package/src/plugins/builtin/backtest/index.tsx +1 -2
- package/src/plugins/builtin/backtest/rules.ts +3 -1
- package/src/plugins/builtin/backtest/window.ts +8 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
- package/src/plugins/builtin/broker-manager/index.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +5 -0
- package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
- package/src/plugins/builtin/buildout/company/id.ts +2 -0
- package/src/plugins/builtin/buildout/company/model.ts +247 -0
- package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
- package/src/plugins/builtin/buildout/facilities.ts +309 -0
- package/src/plugins/builtin/buildout/format.ts +9 -0
- package/src/plugins/builtin/buildout/index.tsx +51 -5
- package/src/plugins/builtin/buildout/model/index.ts +62 -10
- package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
- package/src/plugins/builtin/buildout/model/types.ts +11 -2
- package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
- package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
- package/src/plugins/builtin/buildout/table-model.ts +26 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
- package/src/plugins/builtin/cds/headless.ts +10 -2
- package/src/plugins/builtin/cds/model.ts +7 -3
- package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
- package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
- package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
- package/src/plugins/builtin/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
- package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
- package/src/plugins/builtin/chart-composer/headless.ts +88 -21
- package/src/plugins/builtin/chart-composer/index.tsx +2 -3
- package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
- package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/controller/index.ts +1 -1
- package/src/plugins/builtin/chat/controller/messages.ts +15 -4
- package/src/plugins/builtin/chat/ghost-user.ts +8 -0
- package/src/plugins/builtin/chat/index.tsx +5 -5
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/index.tsx +1 -2
- package/src/plugins/builtin/cloud/plugin.tsx +6 -4
- package/src/plugins/builtin/cloud/team/module.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
- package/src/plugins/builtin/comment-letters/index.tsx +1 -2
- package/src/plugins/builtin/commodity-balances/client.ts +4 -1
- package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
- package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
- package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
- package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
- package/src/plugins/builtin/congress-trades/headless.ts +2 -0
- package/src/plugins/builtin/congress-trades/model.ts +92 -64
- package/src/plugins/builtin/congress-trades/table.tsx +14 -2
- package/src/plugins/builtin/connections/index.ts +2 -3
- package/src/plugins/builtin/cot/client.ts +28 -2
- package/src/plugins/builtin/cot/headless.ts +4 -1
- package/src/plugins/builtin/cot/index.tsx +5 -4
- package/src/plugins/builtin/cot/model.ts +58 -7
- package/src/plugins/builtin/cot/pane.tsx +6 -2
- package/src/plugins/builtin/cpi/client.ts +6 -5
- package/src/plugins/builtin/cpi/index.tsx +1 -2
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/client.ts +7 -2
- package/src/plugins/builtin/credit-boards/headless.ts +25 -12
- package/src/plugins/builtin/credit-boards/model.ts +29 -0
- package/src/plugins/builtin/credit-boards/series.ts +3 -4
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/credit-conditions/client.ts +22 -14
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- package/src/plugins/builtin/crypto-board/client.ts +5 -5
- package/src/plugins/builtin/crypto-board/headless.ts +4 -3
- package/src/plugins/builtin/crypto-board/index.tsx +4 -2
- package/src/plugins/builtin/crypto-board/series.ts +6 -4
- package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
- package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
- package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
- package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
- package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
- package/src/plugins/builtin/debt-maturities/model.ts +17 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
- package/src/plugins/builtin/debt-maturities/series.ts +3 -3
- package/src/plugins/builtin/debug/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/client.ts +32 -2
- package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
- package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
- package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
- package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
- package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
- package/src/plugins/builtin/dividend-yield/view.ts +21 -0
- package/src/plugins/builtin/docs/index.tsx +2 -3
- package/src/plugins/builtin/doe/client.ts +6 -5
- package/src/plugins/builtin/doe/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/doe/prices.ts +2 -3
- package/src/plugins/builtin/doe/series.ts +4 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
- package/src/plugins/builtin/earnings-calls/data.ts +19 -11
- package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
- package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
- package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
- package/src/plugins/builtin/econ/calendar-model.ts +29 -6
- package/src/plugins/builtin/econ/detail-view.tsx +16 -2
- package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +1 -0
- package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
- package/src/plugins/builtin/equity-screener/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/model.ts +28 -4
- package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
- package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
- package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
- package/src/plugins/builtin/executives/data.ts +6 -1
- package/src/plugins/builtin/executives/pane.tsx +9 -21
- package/src/plugins/builtin/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/cache.ts +4 -0
- package/src/plugins/builtin/fear-greed/cli.ts +44 -0
- package/src/plugins/builtin/fear-greed/index.tsx +5 -53
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
- package/src/plugins/builtin/fear-greed/series.ts +6 -4
- package/src/plugins/builtin/fixed-income/index.tsx +1 -2
- package/src/plugins/builtin/futures/board-reads.ts +25 -0
- package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
- package/src/plugins/builtin/futures/index.tsx +15 -0
- package/src/plugins/builtin/futures/model.ts +14 -0
- package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
- package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
- package/src/plugins/builtin/futures-curve/headless.ts +6 -1
- package/src/plugins/builtin/futures-curve/index.tsx +1 -2
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +51 -12
- package/src/plugins/builtin/gpu/client.ts +19 -3
- package/src/plugins/builtin/gpu/equities.tsx +5 -2
- package/src/plugins/builtin/gpu/headless.ts +14 -2
- package/src/plugins/builtin/gpu/index.tsx +1 -2
- package/src/plugins/builtin/gpu/model.ts +20 -2
- package/src/plugins/builtin/gpu/pane.tsx +1 -1
- package/src/plugins/builtin/gpu/series.ts +2 -3
- package/src/plugins/builtin/help/index.tsx +1 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
- package/src/plugins/builtin/holders/index-holders.ts +24 -12
- package/src/plugins/builtin/holders/index.tsx +2 -3
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
- package/src/plugins/builtin/iv-history/index.tsx +1 -2
- package/src/plugins/builtin/jodi/client.ts +4 -1
- package/src/plugins/builtin/jodi/index.tsx +1 -2
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
- package/src/plugins/builtin/layout-manager/index.tsx +9 -8
- package/src/plugins/builtin/macro-day/pane.tsx +4 -2
- package/src/plugins/builtin/market-halts/index.tsx +1 -2
- package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
- package/src/plugins/builtin/market-movers/client.ts +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +16 -5
- package/src/plugins/builtin/market-movers/screener.ts +23 -6
- package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
- package/src/plugins/builtin/market-valuation/index.tsx +1 -2
- package/src/plugins/builtin/members/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/pane.tsx +2 -1
- package/src/plugins/builtin/money-markets/series.ts +3 -5
- package/src/plugins/builtin/news/headless.ts +7 -0
- package/src/plugins/builtin/news/wire/index.tsx +1 -1
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
- package/src/plugins/builtin/notes/index.tsx +3 -6
- package/src/plugins/builtin/openfda/index.tsx +1 -2
- package/src/plugins/builtin/options/view.tsx +6 -5
- package/src/plugins/builtin/options-calculator/index.tsx +1 -2
- package/src/plugins/builtin/options-positioning/index.tsx +1 -2
- package/src/plugins/builtin/options-scenario/index.tsx +2 -4
- package/src/plugins/builtin/perps/index.tsx +1 -2
- package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
- package/src/plugins/builtin/plugin-module.ts +16 -2
- package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
- package/src/plugins/builtin/power/gas-client.ts +2 -1
- package/src/plugins/builtin/power/index.tsx +1 -2
- package/src/plugins/builtin/power/prices-client.ts +3 -2
- package/src/plugins/builtin/rate-path/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/model.ts +16 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
- package/src/plugins/builtin/research/analyst-headless.ts +33 -6
- package/src/plugins/builtin/research/analyst-model.ts +37 -2
- package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
- package/src/plugins/builtin/research/index.tsx +5 -8
- package/src/plugins/builtin/research-search/index.tsx +1 -2
- package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
- package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
- package/src/plugins/builtin/risk-factors/data.ts +8 -4
- package/src/plugins/builtin/risk-factors/index.tsx +3 -1
- package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
- package/src/plugins/builtin/roadmap/index.tsx +2 -3
- package/src/plugins/builtin/roadmap/pane.tsx +49 -2
- package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
- package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
- package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
- package/src/plugins/builtin/scanner/lock-note.ts +26 -3
- package/src/plugins/builtin/sec/filing-content.ts +38 -6
- package/src/plugins/builtin/sec/headless.ts +5 -1
- package/src/plugins/builtin/sec/index.tsx +4 -2
- package/src/plugins/builtin/sec/model.ts +2 -0
- package/src/plugins/builtin/sectors/index.tsx +11 -5
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
- package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
- package/src/plugins/builtin/shared/forming-bars.ts +29 -0
- package/src/plugins/builtin/shared/fred-points.ts +6 -1
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- package/src/plugins/builtin/shared/series-warning.ts +13 -4
- package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
- package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/series.ts +2 -3
- package/src/plugins/builtin/social-mentions/client.ts +15 -10
- package/src/plugins/builtin/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- package/src/plugins/builtin/social-mentions/series.ts +2 -3
- package/src/plugins/builtin/supply-chain/client.ts +22 -1
- package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
- package/src/plugins/builtin/supply-chain/index.tsx +1 -2
- package/src/plugins/builtin/thirteenf/api.ts +127 -29
- package/src/plugins/builtin/thirteenf/data.ts +165 -35
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
- package/src/plugins/builtin/thirteenf/types.ts +14 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
- package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
- package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
- package/src/plugins/builtin/time-sales/index.tsx +1 -2
- package/src/plugins/builtin/time-sales/pane.tsx +3 -1
- package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
- package/src/plugins/builtin/trace-bonds/model.ts +44 -10
- package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
- package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
- package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
- package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
- package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
- package/src/plugins/builtin/vol-surface/client.ts +18 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -2
- package/src/plugins/builtin/volatility/client.ts +43 -16
- package/src/plugins/builtin/volatility/headless.ts +5 -2
- package/src/plugins/builtin/volatility/index.tsx +1 -2
- package/src/plugins/builtin/volatility/model.ts +17 -6
- package/src/plugins/builtin/volatility/pane.tsx +6 -2
- package/src/plugins/builtin/volatility/series.ts +2 -1
- package/src/plugins/builtin/world-indices/footer.ts +6 -1
- package/src/plugins/builtin/world-venue-map/client.ts +10 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -2
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
- package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
- package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
- package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
- package/src/plugins/builtin/yield-curve/client.ts +4 -6
- package/src/plugins/builtin/yield-curve/curves.ts +16 -2
- package/src/plugins/builtin/yield-curve/headless.ts +3 -3
- package/src/plugins/bundle.ts +52 -2
- package/src/plugins/catalog-browser.ts +2 -2
- package/src/plugins/catalog-cli.ts +20 -0
- package/src/plugins/host-link.ts +13 -1
- package/src/plugins/installer.ts +64 -42
- package/src/plugins/loader.ts +43 -11
- package/src/plugins/peer-imports.ts +201 -0
- package/src/plugins/peer-retry.ts +26 -29
- package/src/plugins/plugin-process.ts +128 -0
- package/src/plugins/redirection-guard.ts +45 -0
- package/src/remote/endpoint.ts +16 -3
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/http-proxy.ts +7 -123
- package/src/renderers/dom/desktop/icons.tsx +5 -2
- package/src/renderers/dom/host/image-surface.tsx +46 -21
- package/src/renderers/dom/styles.css +7 -0
- package/src/renderers/dom/toast-host.tsx +2 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
- package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
- package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
- package/src/renderers/electrobun/bun/window/attention.ts +54 -0
- package/src/renderers/electrobun/bun/window/create.ts +6 -2
- package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
- package/src/renderers/electrobun/view/main.tsx +7 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/renderers/electrobun/view/ui-host.tsx +9 -2
- package/src/renderers/opentui/browser-open.ts +10 -4
- package/src/renderers/opentui/image/surface.tsx +1 -1
- package/src/renderers/opentui/start.tsx +12 -6
- package/src/renderers/opentui/toast-host.tsx +2 -1
- package/src/series/registry.ts +18 -0
- package/src/series/resolve.ts +2 -1
- package/src/series/standalone.ts +10 -1
- package/src/series/types.ts +12 -0
- package/src/series/validate.ts +11 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
- package/src/sources/gloomberb-cloud/index.ts +28 -5
- package/src/sources/gloomberb-cloud/news.ts +24 -3
- package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
- package/src/sources/provider-errors.ts +26 -3
- package/src/sources/provider-router/history.ts +5 -0
- package/src/sources/provider-router/index.ts +13 -0
- package/src/sync/core-contributors.ts +32 -0
- package/src/telemetry/attention-counts.ts +64 -6
- package/src/telemetry/attention-setting.ts +33 -0
- package/src/telemetry/crash-reports-node.ts +3 -1
- package/src/telemetry/stall-clocks.ts +93 -0
- package/src/telemetry/stall-reports-node.ts +25 -9
- package/src/telemetry/stall-reports.ts +218 -13
- package/src/test-support/outage.ts +8 -0
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name-listing.ts +59 -29
- package/src/tickers/company-name.ts +84 -1
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +104 -8
- package/src/tickers/search/index.ts +5 -2
- package/src/tickers/search/types.ts +6 -1
- package/src/time-series/bar-aggregation.ts +7 -1
- package/src/time-series/base-series.ts +3 -3
- package/src/time-series/chart-bounds.ts +1 -1
- package/src/time-series/chart-data.ts +5 -2
- package/src/time-series/chart-seed.ts +1 -1
- package/src/time-series/coin-session-close.ts +211 -0
- package/src/time-series/live-bars.ts +13 -2
- package/src/time-series/live-quotes.ts +1 -1
- package/src/time-series/market.ts +5 -3
- package/src/time-series/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +75 -8
- package/src/time-series/resolve.ts +32 -4
- package/src/time-series/series-presentation.ts +1 -1
- package/src/time-series/studies/formula.ts +205 -0
- package/src/time-series/studies/indicators.ts +299 -0
- package/src/time-series/studies/leverage.ts +75 -0
- package/src/time-series/studies/pair.ts +138 -0
- package/src/time-series/studies/resolve.ts +333 -0
- package/src/time-series/studies/shared.ts +141 -0
- package/src/time-series/studies/units.ts +243 -0
- package/src/time-series/studies/vwap.ts +274 -0
- package/src/time-series/types.ts +7 -0
- package/src/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +40 -3
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +17 -1
- package/src/ui/toast.tsx +26 -1
- package/src/utils/ascii-text.ts +140 -0
- package/src/utils/cli-output.ts +91 -18
- package/src/utils/concurrency.ts +24 -0
- package/src/utils/exchanges.ts +6 -0
- package/src/utils/external-url.ts +20 -7
- package/src/utils/financial-statements.ts +73 -16
- package/src/utils/fundamentals.ts +17 -5
- package/src/utils/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/lease-liabilities.ts +54 -0
- package/src/utils/main-thread-stall.ts +122 -5
- package/src/utils/perf-marks.ts +58 -12
- package/src/utils/price-history-integrity.ts +47 -1
- package/src/utils/private-files.ts +141 -0
- package/src/utils/public-address.ts +99 -0
- package/src/account/upgrade-personalization.ts +0 -57
- package/src/account/upgrade-tickers.ts +0 -124
- package/src/api-client/upgrade-experiment.ts +0 -91
- package/src/plugins/builtin/cloud/browser.tsx +0 -3
- package/src/time-series/studies.ts +0 -1628
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logFailure } from "../utils/log-failure";
|
|
2
|
+
import { servedFromSavedCopy } from "../data/saved-copies";
|
|
2
3
|
import { hasValidQuoteObservationTime } from "../market-data/quotes/freshness";
|
|
3
4
|
import { delistingLabel } from "../market-data/quotes/delisting";
|
|
4
5
|
import { financialPeriodCoverage, financialPeriodCoverageWarnings, limitSeriesObservations } from "./financial-period-coverage";
|
|
@@ -42,7 +43,7 @@ import { FORWARD_PE_BASIS_NOTICE, REALIZED_NTM_PE_BASIS_NOTICE } from "./forward
|
|
|
42
43
|
import { valuationPriceWarning } from "./valuation-price";
|
|
43
44
|
import { VALUATION_CURRENCY_WARNING_PREFIX } from "./valuation-currency";
|
|
44
45
|
import { chartPriceHistoryIntegrityNotices } from "./market";
|
|
45
|
-
import { maxStudyWarmupPoints, resolveStudies } from "./studies";
|
|
46
|
+
import { maxStudyWarmupPoints, resolveStudies } from "./studies/resolve";
|
|
46
47
|
import { clipSeriesToWindow } from "./alignment";
|
|
47
48
|
import { totalReturnSeries } from "./total-return";
|
|
48
49
|
import {
|
|
@@ -54,6 +55,7 @@ import {
|
|
|
54
55
|
totalReturnLevelsNotice,
|
|
55
56
|
} from "./price-comparison";
|
|
56
57
|
import { chartQuoteOverrideKeyForSource } from "./live-quotes";
|
|
58
|
+
import { coinLegsAtSessionClose } from "./coin-session-close";
|
|
57
59
|
import { chartSeriesSourceKey } from "../capabilities/chart-series";
|
|
58
60
|
import { rememberSeedListingFacts } from "./parsed-history-cache";
|
|
59
61
|
import {
|
|
@@ -827,7 +829,7 @@ async function resolveChartSpecPass(
|
|
|
827
829
|
cache.capabilitySeriesByRequest.set(key, pending);
|
|
828
830
|
cache.revision += 1;
|
|
829
831
|
}
|
|
830
|
-
return baseCapabilitySeries(seriesSpec, await pending, index);
|
|
832
|
+
return asSavedCopy(baseCapabilitySeries(seriesSpec, await pending, index));
|
|
831
833
|
}
|
|
832
834
|
if (seriesSpec.source.kind === "economic") {
|
|
833
835
|
const request: FredSeriesRequest = {
|
|
@@ -845,6 +847,8 @@ async function resolveChartSpecPass(
|
|
|
845
847
|
priorityWarnings.push(`${result.label}: ${coverageNotice}`);
|
|
846
848
|
coverageNotices.push(`${result.label}: ${coverageNotice}`);
|
|
847
849
|
}
|
|
850
|
+
// A saved copy that answered for a failed refresh is dated in the footer, as every pane's is.
|
|
851
|
+
if (result && fred.source === "stale-fallback") return asSavedCopy({ ...result, savedAt: fred.fetchedAt });
|
|
848
852
|
const freshnessWarning = staleFredWarning(fred);
|
|
849
853
|
if (result && freshnessWarning) priorityWarnings.push(`${result.label}: ${freshnessWarning}`);
|
|
850
854
|
return result;
|
|
@@ -1054,8 +1058,16 @@ async function resolveChartSpecPass(
|
|
|
1054
1058
|
const commonCadence = servedCadences.size === 1 ? [...servedCadences][0] : undefined;
|
|
1055
1059
|
const resolution = commonCadence ?? undefined;
|
|
1056
1060
|
const comparisonResolution = resolution ?? "auto";
|
|
1057
|
-
const
|
|
1058
|
-
|
|
1061
|
+
const comparisonOptions = { preset: hasExplicitWindow ? null : spec.viewport.range, now: referenceNow.getTime() + Math.max(0, Date.now() - resolutionStartedAt) };
|
|
1062
|
+
let priceComparison = resolvePriceComparison(spec, rawSeries, bounds, comparisonResolution, requestBounds ? null : spec.viewport.dateWindow, comparisonOptions);
|
|
1063
|
+
// A coin compared in daily closes with a listing that closes each day is measured at that close when its hourly bars reach.
|
|
1064
|
+
const timed = sources.dataProvider && comparisonResolution === "1d"
|
|
1065
|
+
? await coinLegsAtSessionClose(priceComparison, rawSeries, sources.dataProvider, comparisonOptions.now) : null;
|
|
1066
|
+
if (timed?.timing.atSessionClose.size) {
|
|
1067
|
+
rawSeries.splice(0, rawSeries.length, ...timed.series);
|
|
1068
|
+
priceComparison = resolvePriceComparison(spec, rawSeries, bounds, comparisonResolution, requestBounds ? null : spec.viewport.dateWindow,
|
|
1069
|
+
{ ...comparisonOptions, atSessionClose: timed.timing.atSessionClose, closedEnds: timed.timing.closedEnds });
|
|
1070
|
+
}
|
|
1059
1071
|
const displayBounds = comparisonDisplayBounds(bounds, priceComparison);
|
|
1060
1072
|
const comparisonBounds = priceComparison && priceComparison.start !== null
|
|
1061
1073
|
? priceComparison : bounds;
|
|
@@ -1180,6 +1192,7 @@ async function resolveChartSpecPass(
|
|
|
1180
1192
|
loading: false,
|
|
1181
1193
|
errors,
|
|
1182
1194
|
warnings: [...new Set([...priorityWarnings, ...warnings])],
|
|
1195
|
+
...savedCopyOf(rawSeries.filter((entry) => visibleSeriesIds.has(entry.id))),
|
|
1183
1196
|
...(coverageNotices.length ? { coverageNotices: [...new Set(coverageNotices)] } : {}),
|
|
1184
1197
|
...(returnBasisNotice ? { returnBasisNotice } : {}),
|
|
1185
1198
|
...(delistings.size ? { delistings: [...delistings.values()] } : {}),
|
|
@@ -1188,3 +1201,18 @@ async function resolveChartSpecPass(
|
|
|
1188
1201
|
...(builtBarSize && rawSeries.some((entry) => entry.barSize === builtBarSize) ? { barSize: builtBarSize } : {}),
|
|
1189
1202
|
};
|
|
1190
1203
|
}
|
|
1204
|
+
|
|
1205
|
+
/**
|
|
1206
|
+
* A series drawn from a copy a failed refresh left answering (`savedAt`),
|
|
1207
|
+
* marked as one, so a command charting it ends with "couldn't refresh; saved
|
|
1208
|
+
* copy from …" as the app's footer says.
|
|
1209
|
+
*/
|
|
1210
|
+
function asSavedCopy(series: ResolvedSeries): ResolvedSeries {
|
|
1211
|
+
return series.savedAt != null ? servedFromSavedCopy(series, series.savedAt) : series;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/** The oldest saved copy among `series` that answered for a failed refresh, which the chart's footer dates. */
|
|
1215
|
+
function savedCopyOf(series: readonly ResolvedSeries[]): { savedAt?: number } {
|
|
1216
|
+
const saved = series.flatMap((entry) => entry.savedAt != null && Number.isFinite(entry.savedAt) ? [entry.savedAt] : []);
|
|
1217
|
+
return saved.length ? { savedAt: Math.min(...saved) } : {};
|
|
1218
|
+
}
|
|
@@ -2,7 +2,7 @@ import { limitSeriesObservations } from "./financial-period-coverage";
|
|
|
2
2
|
import type { ManualChartResolution } from "./resolution";
|
|
3
3
|
import { getTimeSeriesField } from "./field-catalog";
|
|
4
4
|
import { collectPriceHistoryIntegrity } from "./market";
|
|
5
|
-
import { activeStudyInputSeriesIds } from "./studies";
|
|
5
|
+
import { activeStudyInputSeriesIds } from "./studies/resolve";
|
|
6
6
|
import { applyResolvedSeriesTransform } from "./transforms";
|
|
7
7
|
import { clipSeriesToWindow } from "./alignment";
|
|
8
8
|
import type { StudyAnchor } from "./leverage";
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { alignTimeSeries } from "../alignment";
|
|
2
|
+
import type { ChartStudySpec, ResolvedSeries, TimeSeriesPoint } from "../types";
|
|
3
|
+
import { isFiniteNumber } from "../../utils/guards";
|
|
4
|
+
import { GALLONS_PER_BARREL, isBarrelsPerTonne, perBarrelUnit, perTonneUnit } from "../price-units";
|
|
5
|
+
import { derivedPoint, outputSeries } from "./shared";
|
|
6
|
+
import {
|
|
7
|
+
combinedUnit,
|
|
8
|
+
derivedValueDecimals,
|
|
9
|
+
fxConversionIssue,
|
|
10
|
+
isPriceGroup,
|
|
11
|
+
knownUnitSignature,
|
|
12
|
+
legName,
|
|
13
|
+
unitMismatchMessage,
|
|
14
|
+
type SeriesUnit,
|
|
15
|
+
} from "./units";
|
|
16
|
+
|
|
17
|
+
interface FormulaLeg {
|
|
18
|
+
/** The input's place in the study's inputs. */
|
|
19
|
+
index: number;
|
|
20
|
+
/** Its multiplier, its sign included. */
|
|
21
|
+
weight: number;
|
|
22
|
+
/** The multiplier carries the 42 gallons that make a per-gallon price per barrel. */
|
|
23
|
+
perBarrel: boolean;
|
|
24
|
+
/** The barrels in a tonne the multiplier carries (7.45 for gasoil), making a per-barrel price per tonne. */
|
|
25
|
+
perTonne: number | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface FormulaTerms {
|
|
29
|
+
/** The inputs the formula adds up. */
|
|
30
|
+
legs: FormulaLeg[];
|
|
31
|
+
/** The inputs the whole sum is multiplied (1) or divided (-1) by: `/EURUSD=X`. */
|
|
32
|
+
factors: Array<{ index: number; power: 1 | -1 }>;
|
|
33
|
+
divisor: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A formula study's terms: `weight1`, `perBarrel1`, `perTonne1` ... per
|
|
38
|
+
* input it adds up, `power1` ... (1 or -1) per input it multiplies or divides
|
|
39
|
+
* the sum by, and an optional `divisor`. A saved or hand-edited spec can carry
|
|
40
|
+
* a multiplier or divisor no formula was written with (a value that is not a
|
|
41
|
+
* number is dropped when the spec loads, so it arrives missing); that is an
|
|
42
|
+
* error, not a silent 1.
|
|
43
|
+
*/
|
|
44
|
+
export function formulaTerms(spec: ChartStudySpec, inputs: readonly ResolvedSeries[]): FormulaTerms | { error: string } {
|
|
45
|
+
const legs: FormulaLeg[] = [];
|
|
46
|
+
const factors: FormulaTerms["factors"] = [];
|
|
47
|
+
for (const [index, input] of inputs.entries()) {
|
|
48
|
+
const power = spec.parameters[`power${index + 1}`];
|
|
49
|
+
if (power !== undefined) {
|
|
50
|
+
if (power !== 1 && power !== -1) return { error: `The formula neither multiplies nor divides by ${legName(input)}; write the formula again.` };
|
|
51
|
+
factors.push({ index, power });
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const weight = spec.parameters[`weight${index + 1}`];
|
|
55
|
+
if (!isFiniteNumber(weight)) return { error: `The multiplier of ${legName(input)} is missing or not a number; write the formula again.` };
|
|
56
|
+
if (weight === 0) return { error: `${legName(input)} has a multiplier of 0, which removes it; leave it out of the formula instead.` };
|
|
57
|
+
const perTonne = spec.parameters[`perTonne${index + 1}`];
|
|
58
|
+
legs.push({
|
|
59
|
+
index,
|
|
60
|
+
weight,
|
|
61
|
+
perBarrel: spec.parameters[`perBarrel${index + 1}`] === 1,
|
|
62
|
+
perTonne: isFiniteNumber(perTonne) && isBarrelsPerTonne(perTonne) ? perTonne : null,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (legs.length === 0) return { error: "The formula has no series to add up, only series it divides by; write the formula again." };
|
|
66
|
+
const divisor = spec.parameters.divisor ?? 1;
|
|
67
|
+
if (!isFiniteNumber(divisor)) return { error: "The formula's divisor is not a number; write the formula again." };
|
|
68
|
+
if (divisor === 0) return { error: "A formula cannot be divided by 0." };
|
|
69
|
+
return { legs, factors, divisor };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A leg's unit once its multiplier is applied: 42 gallons of a per-gallon
|
|
74
|
+
* price are a barrel's worth, and 7.45 barrels of gasoil a tonne's.
|
|
75
|
+
*/
|
|
76
|
+
function formulaLegUnit(leg: FormulaLeg, input: ResolvedSeries): string {
|
|
77
|
+
const unit = leg.perBarrel ? perBarrelUnit(input.unit) : input.unit;
|
|
78
|
+
return leg.perTonne ? perTonneUnit(unit) : unit;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Why a formula's legs cannot be summed, or null when every leg counts the same known unit. */
|
|
82
|
+
function formulaUnitMismatch(terms: FormulaTerms, inputs: readonly ResolvedSeries[]): string | null {
|
|
83
|
+
if (terms.legs.length < 2) return null;
|
|
84
|
+
const legs = terms.legs.map((leg) => ({ series: inputs[leg.index]!, unit: formulaLegUnit(leg, inputs[leg.index]!) }));
|
|
85
|
+
const signatures = legs.map(({ series, unit }) => knownUnitSignature({ unit, unitGroup: series.unitGroup }));
|
|
86
|
+
const other = signatures.findIndex((signature, index) => index > 0 && (signature === null || signature !== signatures[0]));
|
|
87
|
+
if (signatures[0] !== null && other < 0) return null;
|
|
88
|
+
return unitMismatchMessage("formula", legs[0]!, legs[Math.max(1, other)]!);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function formatFormulaNumber(value: number): string {
|
|
92
|
+
return String(Number(value.toPrecision(10)));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A formula as the legend names it, from the terms it was written with:
|
|
97
|
+
* `42×HO=F - CL=F`, `(2×42×RB=F + 42×HO=F - 3×CL=F)/3`, `(42×HO=F - BZ=F)/EURUSD=X`.
|
|
98
|
+
*/
|
|
99
|
+
function formulaLabel(inputs: readonly ResolvedSeries[], terms: FormulaTerms): string {
|
|
100
|
+
const sum = terms.legs.map((leg, position) => {
|
|
101
|
+
const magnitude = Math.abs(leg.weight);
|
|
102
|
+
// The conversions it carries are named as written: 7.45×42×HO=F.
|
|
103
|
+
const conversions = [...(leg.perTonne ? [leg.perTonne] : []), ...(leg.perBarrel ? [GALLONS_PER_BARREL] : [])];
|
|
104
|
+
const count = conversions.reduce((rest, factor) => rest / factor, magnitude);
|
|
105
|
+
const factors = [...(Math.abs(count - 1) < 1e-9 ? [] : [formatFormulaNumber(count)]), ...conversions.map(String)];
|
|
106
|
+
const term = [...factors, legName(inputs[leg.index]!)].join("×");
|
|
107
|
+
if (position === 0) return leg.weight < 0 ? `-${term}` : term;
|
|
108
|
+
return `${leg.weight < 0 ? "-" : "+"} ${term}`;
|
|
109
|
+
}).join(" ");
|
|
110
|
+
if (terms.divisor === 1 && terms.factors.length === 0) return sum;
|
|
111
|
+
return [
|
|
112
|
+
`(${sum})`,
|
|
113
|
+
...(terms.divisor === 1 ? [] : [`/${formatFormulaNumber(terms.divisor)}`]),
|
|
114
|
+
...terms.factors.map(({ index, power }) => `${power < 0 ? "/" : "×"}${legName(inputs[index]!)}`),
|
|
115
|
+
].join("");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Every input's value on each date they share, each carried forward to the others' dates. */
|
|
119
|
+
function alignedSamples(inputs: readonly ResolvedSeries[]): Array<{ point: TimeSeriesPoint; values: number[] }> {
|
|
120
|
+
return alignTimeSeries(inputs, { mode: "intersection", carryForward: true }).flatMap((row) => {
|
|
121
|
+
const aligned = inputs.map((input) => row.values[input.id]);
|
|
122
|
+
if (aligned.some((entry) => !isFiniteNumber(entry?.value))) return [];
|
|
123
|
+
const availability = Math.max(...aligned.map((entry) => entry!.point.availableAt?.getTime() ?? entry!.point.date.getTime()));
|
|
124
|
+
return [{
|
|
125
|
+
values: aligned.map((entry) => entry!.value as number),
|
|
126
|
+
point: {
|
|
127
|
+
date: new Date(row.date),
|
|
128
|
+
observedAt: new Date(row.date),
|
|
129
|
+
availableAt: Number.isFinite(availability) ? new Date(availability) : undefined,
|
|
130
|
+
value: null,
|
|
131
|
+
provenance: { quality: "derived" as const },
|
|
132
|
+
},
|
|
133
|
+
}];
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* A weighted sum of the legs over a divisor, in the unit the legs share once
|
|
139
|
+
* converted, then times or over the series it is scaled by, in the unit
|
|
140
|
+
* that arithmetic leaves: a crack in USD / barrel over EURUSD=X is EUR / barrel.
|
|
141
|
+
*/
|
|
142
|
+
/**
|
|
143
|
+
* A formula's unit: its legs' once converted, then times or over each series
|
|
144
|
+
* it is scaled by. Legs that count different units, or an FX rate the wrong
|
|
145
|
+
* way up, are an error that says why: a crack over USDJPY=X is no price, and
|
|
146
|
+
* times it JPY / barrel.
|
|
147
|
+
*/
|
|
148
|
+
export function formulaUnit(terms: FormulaTerms, inputs: readonly ResolvedSeries[]): SeriesUnit | { error: string } {
|
|
149
|
+
const mismatch = formulaUnitMismatch(terms, inputs);
|
|
150
|
+
if (mismatch) return { error: mismatch };
|
|
151
|
+
const first = inputs[terms.legs[0]!.index]!;
|
|
152
|
+
let unit: SeriesUnit = { unit: formulaLegUnit(terms.legs[0]!, first), unitGroup: first.unitGroup };
|
|
153
|
+
for (const factor of terms.factors) {
|
|
154
|
+
const series = inputs[factor.index]!;
|
|
155
|
+
const next = combinedUnit(unit, series, factor.power);
|
|
156
|
+
const issue = fxConversionIssue(unit, series, factor.power, next);
|
|
157
|
+
if (issue) return { error: issue };
|
|
158
|
+
unit = next;
|
|
159
|
+
}
|
|
160
|
+
return unit;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function resolveFormula(
|
|
164
|
+
spec: ChartStudySpec,
|
|
165
|
+
inputs: readonly ResolvedSeries[],
|
|
166
|
+
terms: FormulaTerms,
|
|
167
|
+
unit: SeriesUnit,
|
|
168
|
+
color: string,
|
|
169
|
+
): ResolvedSeries[] {
|
|
170
|
+
const first = inputs[terms.legs[0]!.index]!;
|
|
171
|
+
// Rounded off the float's last digits: 42 × 4.6233 - 96.12 is 98.0586, not 98.05860000000001.
|
|
172
|
+
const points = alignedSamples(inputs).map(({ point, values }) => {
|
|
173
|
+
let value: number | null = terms.legs.reduce((sum, leg) => sum + values[leg.index]! * leg.weight, 0) / terms.divisor;
|
|
174
|
+
for (const factor of terms.factors) {
|
|
175
|
+
const scale = values[factor.index]!;
|
|
176
|
+
value = value === null || (factor.power < 0 && scale === 0) ? null : factor.power < 0 ? value / scale : value * scale;
|
|
177
|
+
}
|
|
178
|
+
return derivedPoint({ point, value: 0 }, value === null ? null : Number(value.toPrecision(12)));
|
|
179
|
+
});
|
|
180
|
+
const frequencies = new Set(inputs.map((input) => input.nativeFrequency));
|
|
181
|
+
const resolutions = new Set(inputs.map((input) => input.historyResolution));
|
|
182
|
+
const scaled = terms.factors.length > 0;
|
|
183
|
+
// A price per quantity reads with its unit beside it, as a future's does.
|
|
184
|
+
const statedUnit = scaled
|
|
185
|
+
? isPriceGroup(unit.unitGroup) && unit.unit.includes("/") ? unit.unit : null
|
|
186
|
+
: inputs.some((input) => input.statedUnit) ? unit.unit : null;
|
|
187
|
+
const valueDecimals = !scaled || isPriceGroup(unit.unitGroup) ? derivedValueDecimals(inputs, points) : undefined;
|
|
188
|
+
return [outputSeries(spec, first, {
|
|
189
|
+
label: formulaLabel(inputs, terms),
|
|
190
|
+
points,
|
|
191
|
+
color,
|
|
192
|
+
unit: unit.unit,
|
|
193
|
+
unitGroup: unit.unitGroup,
|
|
194
|
+
statedUnit,
|
|
195
|
+
// A weighted sum is no traded price: its decimals are its legs', not a contract's tick.
|
|
196
|
+
priceAssetCategory: null,
|
|
197
|
+
...(valueDecimals !== undefined ? { valueDecimals } : {}),
|
|
198
|
+
// As the pair formulas: as-of levels, constant until an input changes.
|
|
199
|
+
style: "step",
|
|
200
|
+
interpolation: "step-after",
|
|
201
|
+
axis: "left",
|
|
202
|
+
nativeFrequency: frequencies.size === 1 ? first.nativeFrequency : "auto",
|
|
203
|
+
historyResolution: resolutions.size === 1 ? first.historyResolution : null,
|
|
204
|
+
})];
|
|
205
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { mergePriceHistoryIntegrity } from "../../utils/price-history-integrity";
|
|
2
|
+
import { isRealizedVolatilityEstimator, rollingRealizedVolatility } from "../../market-data/realized-volatility";
|
|
3
|
+
import { averageTrueRange } from "../trader-studies";
|
|
4
|
+
import type { ChartStudySpec, ResolvedSeries, TimeSeriesPoint } from "../types";
|
|
5
|
+
import { isFiniteNumber } from "../../utils/guards";
|
|
6
|
+
import {
|
|
7
|
+
derivedPoint,
|
|
8
|
+
outputSeries,
|
|
9
|
+
positiveInteger,
|
|
10
|
+
samplesFor,
|
|
11
|
+
STUDY_COLORS,
|
|
12
|
+
studyBars,
|
|
13
|
+
studyPeriod,
|
|
14
|
+
type NumericSample,
|
|
15
|
+
} from "./shared";
|
|
16
|
+
|
|
17
|
+
export interface IndexedValue {
|
|
18
|
+
index: number;
|
|
19
|
+
value: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Exported so backtest rules and chart studies share identical math. */
|
|
23
|
+
export function sma(values: readonly number[], period: number): IndexedValue[] {
|
|
24
|
+
if (values.length < period) return [];
|
|
25
|
+
const result: IndexedValue[] = [];
|
|
26
|
+
let sum = 0;
|
|
27
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
28
|
+
sum += values[index]!;
|
|
29
|
+
if (index >= period) sum -= values[index - period]!;
|
|
30
|
+
if (index >= period - 1) result.push({ index, value: sum / period });
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function ema(values: readonly number[], period: number): IndexedValue[] {
|
|
36
|
+
if (values.length < period) return [];
|
|
37
|
+
const result: IndexedValue[] = [];
|
|
38
|
+
let current = values.slice(0, period).reduce((sum, value) => sum + value, 0) / period;
|
|
39
|
+
result.push({ index: period - 1, value: current });
|
|
40
|
+
const multiplier = 2 / (period + 1);
|
|
41
|
+
for (let index = period; index < values.length; index += 1) {
|
|
42
|
+
current = values[index]! * multiplier + current * (1 - multiplier);
|
|
43
|
+
result.push({ index, value: current });
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function rsi(values: readonly number[], period: number): IndexedValue[] {
|
|
49
|
+
if (values.length < period + 1) return [];
|
|
50
|
+
let averageGain = 0;
|
|
51
|
+
let averageLoss = 0;
|
|
52
|
+
for (let index = 1; index <= period; index += 1) {
|
|
53
|
+
const change = values[index]! - values[index - 1]!;
|
|
54
|
+
if (change > 0) averageGain += change;
|
|
55
|
+
else averageLoss -= change;
|
|
56
|
+
}
|
|
57
|
+
averageGain /= period;
|
|
58
|
+
averageLoss /= period;
|
|
59
|
+
const result: IndexedValue[] = [{
|
|
60
|
+
index: period,
|
|
61
|
+
value: averageLoss === 0 ? 100 : 100 - 100 / (1 + averageGain / averageLoss),
|
|
62
|
+
}];
|
|
63
|
+
for (let index = period + 1; index < values.length; index += 1) {
|
|
64
|
+
const change = values[index]! - values[index - 1]!;
|
|
65
|
+
averageGain = (averageGain * (period - 1) + Math.max(change, 0)) / period;
|
|
66
|
+
averageLoss = (averageLoss * (period - 1) + Math.max(-change, 0)) / period;
|
|
67
|
+
result.push({
|
|
68
|
+
index,
|
|
69
|
+
value: averageLoss === 0 ? 100 : 100 - 100 / (1 + averageGain / averageLoss),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function indexedPoints(samples: readonly NumericSample[], values: readonly IndexedValue[]): TimeSeriesPoint[] {
|
|
76
|
+
return values.flatMap(({ index, value }) => {
|
|
77
|
+
const sample = samples[index];
|
|
78
|
+
return sample ? [derivedPoint(sample, value)] : [];
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function studyWarmupPoints(spec: ChartStudySpec): number {
|
|
83
|
+
if (spec.kind === "realized-vol") {
|
|
84
|
+
const window = positiveInteger(spec.parameters.window, 30);
|
|
85
|
+
return spec.parameters.estimator === undefined || spec.parameters.estimator === "close-to-close" || spec.parameters.estimator === "yang-zhang"
|
|
86
|
+
? window : window - 1;
|
|
87
|
+
}
|
|
88
|
+
if (spec.kind === "sma" || spec.kind === "ema" || spec.kind === "bollinger") {
|
|
89
|
+
return studyPeriod(spec, 20) - 1;
|
|
90
|
+
}
|
|
91
|
+
if (spec.kind === "rsi") return studyPeriod(spec, 14);
|
|
92
|
+
if (spec.kind === "macd") {
|
|
93
|
+
const slow = positiveInteger(spec.parameters.slow, 26);
|
|
94
|
+
const signal = positiveInteger(spec.parameters.signal, 9);
|
|
95
|
+
return slow + signal - 2;
|
|
96
|
+
}
|
|
97
|
+
if (spec.kind === "correlation") return studyPeriod(spec, 20);
|
|
98
|
+
if (spec.kind === "atr") return studyPeriod(spec, 14) - 1;
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function resolveRealizedVolatility(spec: ChartStudySpec, input: ResolvedSeries, color: string): ResolvedSeries[] {
|
|
103
|
+
const window = positiveInteger(spec.parameters.window, 30);
|
|
104
|
+
const estimator = isRealizedVolatilityEstimator(spec.parameters.estimator) ? spec.parameters.estimator : "close-to-close";
|
|
105
|
+
// Retain invalid rows and the last correction for each date. Dropping one
|
|
106
|
+
// would turn two separated returns into adjacent observations.
|
|
107
|
+
const byDate = new Map(input.points.map((point) => [point.date.getTime(), point]));
|
|
108
|
+
const source = [...byDate.values()].sort((a, b) => a.date.getTime() - b.date.getTime());
|
|
109
|
+
const calculated = rollingRealizedVolatility(source.map((point) => ({
|
|
110
|
+
date: point.date,
|
|
111
|
+
close: point.provenance?.priceHistoryIntegrity || point.provenance?.valuationPriceIssues?.length
|
|
112
|
+
? Number.NaN : (point.close === undefined ? point.value : point.close) ?? Number.NaN,
|
|
113
|
+
open: point.open ?? undefined,
|
|
114
|
+
high: point.high ?? undefined,
|
|
115
|
+
low: point.low ?? undefined,
|
|
116
|
+
})), { windows: [window], estimator });
|
|
117
|
+
const warmup = studyWarmupPoints(spec);
|
|
118
|
+
const points = calculated.map((point, index): TimeSeriesPoint => {
|
|
119
|
+
const original = source[index]!;
|
|
120
|
+
const value = point.values[window];
|
|
121
|
+
const derived = derivedPoint({ point: original, value: 0 }, value == null ? null : value * 100);
|
|
122
|
+
if (value == null) {
|
|
123
|
+
const affected = source.slice(Math.max(0, index - warmup), index + 1);
|
|
124
|
+
const integrity = affected.flatMap((entry) => entry.provenance?.priceHistoryIntegrity ? [entry.provenance.priceHistoryIntegrity] : []);
|
|
125
|
+
if (integrity.length) derived.provenance!.priceHistoryIntegrity = mergePriceHistoryIntegrity(...integrity);
|
|
126
|
+
}
|
|
127
|
+
return derived;
|
|
128
|
+
});
|
|
129
|
+
return [outputSeries(spec, input, {
|
|
130
|
+
label: `RV(${window}, ${estimator}) ${input.label}`,
|
|
131
|
+
points,
|
|
132
|
+
color,
|
|
133
|
+
unit: "%",
|
|
134
|
+
unitGroup: "percent",
|
|
135
|
+
axis: "left",
|
|
136
|
+
})];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function resolveMovingAverage(
|
|
140
|
+
spec: ChartStudySpec,
|
|
141
|
+
input: ResolvedSeries,
|
|
142
|
+
color: string,
|
|
143
|
+
exponential: boolean,
|
|
144
|
+
): ResolvedSeries[] {
|
|
145
|
+
const period = studyPeriod(spec, 20);
|
|
146
|
+
const samples = samplesFor(input);
|
|
147
|
+
const calculated = exponential
|
|
148
|
+
? ema(samples.map(({ value }) => value), period)
|
|
149
|
+
: sma(samples.map(({ value }) => value), period);
|
|
150
|
+
const name = exponential ? "EMA" : "SMA";
|
|
151
|
+
return [outputSeries(spec, input, {
|
|
152
|
+
label: `${name}(${period}) ${input.label}`,
|
|
153
|
+
points: indexedPoints(samples, calculated),
|
|
154
|
+
color,
|
|
155
|
+
})];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function resolveBollinger(
|
|
159
|
+
spec: ChartStudySpec,
|
|
160
|
+
input: ResolvedSeries,
|
|
161
|
+
color: string,
|
|
162
|
+
): ResolvedSeries[] {
|
|
163
|
+
const period = studyPeriod(spec, 20);
|
|
164
|
+
const deviations = isFiniteNumber(spec.parameters.stdDev) && spec.parameters.stdDev > 0
|
|
165
|
+
? spec.parameters.stdDev
|
|
166
|
+
: 2;
|
|
167
|
+
const samples = samplesFor(input);
|
|
168
|
+
const values = samples.map(({ value }) => value);
|
|
169
|
+
const middle = sma(values, period);
|
|
170
|
+
const upper: IndexedValue[] = [];
|
|
171
|
+
const lower: IndexedValue[] = [];
|
|
172
|
+
for (const point of middle) {
|
|
173
|
+
const window = values.slice(point.index - period + 1, point.index + 1);
|
|
174
|
+
const variance = window.reduce((sum, value) => sum + (value - point.value) ** 2, 0) / period;
|
|
175
|
+
const deviation = Math.sqrt(variance) * deviations;
|
|
176
|
+
upper.push({ index: point.index, value: point.value + deviation });
|
|
177
|
+
lower.push({ index: point.index, value: point.value - deviation });
|
|
178
|
+
}
|
|
179
|
+
const label = `Bollinger(${period},${deviations}) ${input.label}`;
|
|
180
|
+
return [
|
|
181
|
+
outputSeries(spec, input, {
|
|
182
|
+
id: `${spec.id}:upper`,
|
|
183
|
+
label: `${label} Upper`,
|
|
184
|
+
points: indexedPoints(samples, upper),
|
|
185
|
+
color,
|
|
186
|
+
}),
|
|
187
|
+
outputSeries(spec, input, {
|
|
188
|
+
id: `${spec.id}:middle`,
|
|
189
|
+
label: `${label} Middle`,
|
|
190
|
+
points: indexedPoints(samples, middle),
|
|
191
|
+
color,
|
|
192
|
+
}),
|
|
193
|
+
outputSeries(spec, input, {
|
|
194
|
+
id: `${spec.id}:lower`,
|
|
195
|
+
label: `${label} Lower`,
|
|
196
|
+
points: indexedPoints(samples, lower),
|
|
197
|
+
color,
|
|
198
|
+
}),
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function resolveRsi(spec: ChartStudySpec, input: ResolvedSeries, color: string): ResolvedSeries[] {
|
|
203
|
+
const period = studyPeriod(spec, 14);
|
|
204
|
+
const samples = samplesFor(input);
|
|
205
|
+
return [outputSeries(spec, input, {
|
|
206
|
+
label: `RSI(${period}) ${input.label}`,
|
|
207
|
+
points: indexedPoints(samples, rsi(samples.map(({ value }) => value), period)),
|
|
208
|
+
color,
|
|
209
|
+
unit: "index",
|
|
210
|
+
unitGroup: "oscillator-0-100",
|
|
211
|
+
axis: "left",
|
|
212
|
+
})];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function resolveMacd(spec: ChartStudySpec, input: ResolvedSeries, color: string): ResolvedSeries[] {
|
|
216
|
+
const fastPeriod = positiveInteger(spec.parameters.fast, 12);
|
|
217
|
+
const slowPeriod = positiveInteger(spec.parameters.slow, 26);
|
|
218
|
+
const signalPeriod = positiveInteger(spec.parameters.signal, 9);
|
|
219
|
+
if (fastPeriod >= slowPeriod) return [];
|
|
220
|
+
const samples = samplesFor(input);
|
|
221
|
+
const values = samples.map(({ value }) => value);
|
|
222
|
+
const fast = new Map(ema(values, fastPeriod).map((point) => [point.index, point.value]));
|
|
223
|
+
const macd = ema(values, slowPeriod).flatMap(({ index, value }) => {
|
|
224
|
+
const fastValue = fast.get(index);
|
|
225
|
+
return fastValue === undefined ? [] : [{ index, value: fastValue - value }];
|
|
226
|
+
});
|
|
227
|
+
const signalOnMacd = ema(macd.map(({ value }) => value), signalPeriod);
|
|
228
|
+
const signal = signalOnMacd.flatMap(({ index, value }) => {
|
|
229
|
+
const source = macd[index];
|
|
230
|
+
return source ? [{ index: source.index, value }] : [];
|
|
231
|
+
});
|
|
232
|
+
const macdByIndex = new Map(macd.map((point) => [point.index, point.value]));
|
|
233
|
+
const histogram = signal.map(({ index, value }) => ({ index, value: macdByIndex.get(index)! - value }));
|
|
234
|
+
const label = `MACD(${fastPeriod},${slowPeriod},${signalPeriod}) ${input.label}`;
|
|
235
|
+
return [
|
|
236
|
+
outputSeries(spec, input, {
|
|
237
|
+
id: `${spec.id}:macd`,
|
|
238
|
+
label,
|
|
239
|
+
points: indexedPoints(samples, macd),
|
|
240
|
+
color,
|
|
241
|
+
axis: "left",
|
|
242
|
+
}),
|
|
243
|
+
outputSeries(spec, input, {
|
|
244
|
+
id: `${spec.id}:signal`,
|
|
245
|
+
label: `${label} Signal`,
|
|
246
|
+
points: indexedPoints(samples, signal),
|
|
247
|
+
color: STUDY_COLORS[1]!,
|
|
248
|
+
axis: "left",
|
|
249
|
+
}),
|
|
250
|
+
outputSeries(spec, input, {
|
|
251
|
+
id: `${spec.id}:histogram`,
|
|
252
|
+
label: `${label} Histogram`,
|
|
253
|
+
points: indexedPoints(samples, histogram),
|
|
254
|
+
color: STUDY_COLORS[4]!,
|
|
255
|
+
style: "columns",
|
|
256
|
+
axis: "left",
|
|
257
|
+
}),
|
|
258
|
+
];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function resolveVolume(spec: ChartStudySpec, input: ResolvedSeries, color: string, nameInput: boolean): ResolvedSeries[] {
|
|
262
|
+
// No bar has volume to plot (spot FX quotes 0 for every bar; a coin's window
|
|
263
|
+
// can miss every bar's), so the study adds no panel rather than an empty one.
|
|
264
|
+
if (input.points.length > 0 && !input.points.some((point) => isFiniteNumber(point.volume) && point.volume !== 0)) return [];
|
|
265
|
+
// Left to its own colour, volume follows the candles: the price's colour for
|
|
266
|
+
// a bar that rose, the falling colour for one that fell. A colour picked for it stays flat.
|
|
267
|
+
const directional = !spec.color;
|
|
268
|
+
const points = [...input.points]
|
|
269
|
+
.sort((left, right) => left.date.getTime() - right.date.getTime())
|
|
270
|
+
.flatMap((point) => isFiniteNumber(point.volume)
|
|
271
|
+
? [{
|
|
272
|
+
...derivedPoint({ point, value: point.volume }, point.volume),
|
|
273
|
+
...(directional && isFiniteNumber(point.open) && isFiniteNumber(point.close) && point.close < point.open ? { fell: true } : {}),
|
|
274
|
+
}]
|
|
275
|
+
: []);
|
|
276
|
+
return [outputSeries(spec, input, {
|
|
277
|
+
label: nameInput ? `Volume ${input.label}` : "Volume",
|
|
278
|
+
points,
|
|
279
|
+
color: directional ? input.color : color,
|
|
280
|
+
unit: input.volumeUnit ?? "",
|
|
281
|
+
unitGroup: "volume",
|
|
282
|
+
style: "columns",
|
|
283
|
+
axis: "left",
|
|
284
|
+
})];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function resolveAtr(spec: ChartStudySpec, input: ResolvedSeries, color: string, nameInput: boolean): ResolvedSeries[] {
|
|
288
|
+
const period = studyPeriod(spec, 14);
|
|
289
|
+
const bars = studyBars(input);
|
|
290
|
+
return [outputSeries(spec, input, {
|
|
291
|
+
label: nameInput ? `ATR(${period}) ${input.label}` : `ATR(${period})`,
|
|
292
|
+
points: averageTrueRange(bars, period).flatMap(({ index, value }) => {
|
|
293
|
+
const bar = bars[index];
|
|
294
|
+
return bar ? [derivedPoint(bar.sample, value)] : [];
|
|
295
|
+
}),
|
|
296
|
+
color,
|
|
297
|
+
axis: "left",
|
|
298
|
+
})];
|
|
299
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ManualChartResolution } from "../resolution";
|
|
2
|
+
import type { ChartStudySpec, ResolvedSeries } from "../types";
|
|
3
|
+
import { studyMessageName } from "../series-name";
|
|
4
|
+
import {
|
|
5
|
+
dailyLeverageGrowth,
|
|
6
|
+
leverageDecayPoints,
|
|
7
|
+
leverageFactorLabel,
|
|
8
|
+
leverageSymbol,
|
|
9
|
+
sessionCloses,
|
|
10
|
+
type StudyAnchor,
|
|
11
|
+
} from "../leverage";
|
|
12
|
+
import { DECAY_COLORS, derivedPoint, outputSeries } from "./shared";
|
|
13
|
+
|
|
14
|
+
/** The objective's line and its decay compound session returns: they need daily bars, whatever the chart's range. */
|
|
15
|
+
export function dailyBarsIssue(spec: ChartStudySpec, input: ResolvedSeries, marketResolution: ManualChartResolution | undefined): string | null {
|
|
16
|
+
const resolution = input.historyResolution === undefined ? marketResolution : input.historyResolution;
|
|
17
|
+
if (input.nativeFrequency === "daily" && (resolution === undefined || resolution === "1d")) return null;
|
|
18
|
+
return `${studyMessageName(spec)}: ${leverageFactorLabel(Number(spec.parameters.factor))} daily needs daily bars. Choose D bars.`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The line a leveraged or inverse fund's daily objective draws from its index
|
|
23
|
+
* fund, in percent from where the chart compares (3× daily QQQ beside TQQQ),
|
|
24
|
+
* on the percent axis the compared legs share.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveLeverageLine(spec: ChartStudySpec, index: ResolvedSeries, color: string, anchor: StudyAnchor | undefined): ResolvedSeries[] {
|
|
27
|
+
const factor = spec.parameters.factor as number;
|
|
28
|
+
const closes = sessionCloses(index, anchor);
|
|
29
|
+
const growth = dailyLeverageGrowth(closes.map((entry) => entry.close), factor);
|
|
30
|
+
return [outputSeries(spec, index, {
|
|
31
|
+
label: `${leverageFactorLabel(factor)} daily ${leverageSymbol(index)}`,
|
|
32
|
+
points: closes.map((entry, position) => derivedPoint({ point: entry.point, value: entry.close }, (growth[position]! - 1) * 100)),
|
|
33
|
+
color,
|
|
34
|
+
unit: "%",
|
|
35
|
+
unitGroup: "percent",
|
|
36
|
+
statedUnit: null,
|
|
37
|
+
priceAssetCategory: null,
|
|
38
|
+
axis: "left",
|
|
39
|
+
})];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* How far the fund falls short of its daily objective, in percentage points
|
|
44
|
+
* from where the chart compares: the fund against the daily line (fees,
|
|
45
|
+
* financing, tracking), and the daily line against the factor times the index
|
|
46
|
+
* fund's return over the period (compounding, which daily resets cost in a
|
|
47
|
+
* market that swings).
|
|
48
|
+
*/
|
|
49
|
+
export function resolveLeverageDecay(
|
|
50
|
+
spec: ChartStudySpec,
|
|
51
|
+
fund: ResolvedSeries,
|
|
52
|
+
index: ResolvedSeries,
|
|
53
|
+
anchors: ReadonlyMap<string, StudyAnchor> | undefined,
|
|
54
|
+
): ResolvedSeries[] {
|
|
55
|
+
const factor = spec.parameters.factor as number;
|
|
56
|
+
const decay = leverageDecayPoints(sessionCloses(fund, anchors?.get(fund.id)), sessionCloses(index, anchors?.get(index.id)), factor);
|
|
57
|
+
const daily = `${leverageFactorLabel(factor)} daily`;
|
|
58
|
+
const line = (id: string, label: string, color: string, value: (point: (typeof decay)[number]) => number) => outputSeries(spec, fund, {
|
|
59
|
+
id: `${spec.id}:${id}`,
|
|
60
|
+
label,
|
|
61
|
+
points: decay.map((entry) => derivedPoint({ point: entry.point, value: entry.fund }, value(entry))),
|
|
62
|
+
color: spec.color ?? color,
|
|
63
|
+
unit: "pp",
|
|
64
|
+
// Not a percent group: a difference of returns reads in points (-0.96 pp), not as a return.
|
|
65
|
+
unitGroup: "pp",
|
|
66
|
+
statedUnit: null,
|
|
67
|
+
priceAssetCategory: null,
|
|
68
|
+
axis: "left",
|
|
69
|
+
});
|
|
70
|
+
return [
|
|
71
|
+
line("fund", `${leverageSymbol(fund)} decay vs ${daily} ${leverageSymbol(index)}`, DECAY_COLORS[0], (entry) => entry.fund - entry.daily),
|
|
72
|
+
// `3× QQQ`: three times QQQ's return over the period, the multiple a daily reset does not deliver.
|
|
73
|
+
line("compounding", `${daily} ${leverageSymbol(index)} decay vs ${leverageFactorLabel(factor)} ${leverageSymbol(index)}`, DECAY_COLORS[1], (entry) => entry.daily - entry.period),
|
|
74
|
+
];
|
|
75
|
+
}
|