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
|
@@ -38,10 +38,18 @@ export interface RiskConversion {
|
|
|
38
38
|
export const FX_UNAVAILABLE = "Foreign holdings: daily FX closes unavailable";
|
|
39
39
|
export const FX_NO_PAIR = "Foreign holdings: no daily FX pair for this currency";
|
|
40
40
|
export const FX_STALE = "Foreign holdings: daily FX history is stale";
|
|
41
|
-
export const FX_GAP = "Foreign holdings: daily FX history has gaps on the holding's dates";
|
|
41
|
+
export const FX_GAP = "Foreign holdings: daily FX history has gaps longer than three days on the holding's dates";
|
|
42
42
|
export const FX_DISAGREES = "Foreign holdings: daily FX closes disagree with the current rate";
|
|
43
43
|
export const LOCAL_GAP = "Foreign holdings: local daily history has a gap longer than a market holiday";
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Most FX trading days in a row (weekdays) a pair may go without a daily
|
|
47
|
+
* close and still be bridged with the last close before them: Good Friday and
|
|
48
|
+
* Easter Monday, 25 December, 1 January, which the daily source skips while
|
|
49
|
+
* US sessions continue. A longer run is missing data and rejects the series.
|
|
50
|
+
*/
|
|
51
|
+
const MAX_BRIDGED_FX_DAYS = 3;
|
|
52
|
+
|
|
45
53
|
/**
|
|
46
54
|
* Longest run of calendar days a listing may go without a close while US
|
|
47
55
|
* sessions continue (Golden Week, Lunar New Year, national holidays). A longer
|
|
@@ -70,6 +78,42 @@ export function accountFxReason(currency: string, problem: string): string {
|
|
|
70
78
|
return `${pair} ${tail ?? "daily FX closes unavailable"}`;
|
|
71
79
|
}
|
|
72
80
|
|
|
81
|
+
/**
|
|
82
|
+
* The FX close a date converts at: the pair's own close that day, or the last
|
|
83
|
+
* close before it across at most MAX_BRIDGED_FX_DAYS weekdays without one
|
|
84
|
+
* (`bridged`). Null when the gap is longer, or no close comes before it.
|
|
85
|
+
*/
|
|
86
|
+
export function bridgedFxClose(rateOn: (date: string) => number | undefined, date: string): { rate: number; bridged: boolean } | null {
|
|
87
|
+
let missing = 0;
|
|
88
|
+
for (let time = Date.parse(`${date}T00:00:00Z`), step = 0; step <= 7 + MAX_BRIDGED_FX_DAYS; step++, time -= DAY_MS) {
|
|
89
|
+
const day = new Date(time).toISOString().slice(0, 10);
|
|
90
|
+
const rate = rateOn(day);
|
|
91
|
+
if (rate != null && rate > 0) return { rate, bridged: day !== date };
|
|
92
|
+
const weekday = new Date(time).getUTCDay();
|
|
93
|
+
if (weekday !== 0 && weekday !== 6 && ++missing > MAX_BRIDGED_FX_DAYS) return null;
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* "GBP/USD has no daily close on 2025-04-18 and 2025-04-21; the returns across
|
|
100
|
+
* them use the close before": what a conversion bridged (bridgedFxClose), said
|
|
101
|
+
* once per pair. Null when it bridged nothing.
|
|
102
|
+
*/
|
|
103
|
+
export function fxBridgeNote(pair: string, dates: Iterable<string>): string | null {
|
|
104
|
+
const days = [...new Set(dates)].sort();
|
|
105
|
+
if (!days.length) return null;
|
|
106
|
+
const shown = days.length > 4 ? [...days.slice(0, 3), `${days.length - 3} more days`] : days;
|
|
107
|
+
const list = shown.length === 1 ? shown[0]! : `${shown.slice(0, -1).join(", ")} and ${shown.at(-1)!}`;
|
|
108
|
+
return `${pair} has no daily close on ${list}; the returns across ${days.length === 1 ? "it use the close before it" : "them use the close before each"}.`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Account returns, and the dates whose FX close was the last one before them (bridgedFxClose). */
|
|
112
|
+
export interface AccountReturns {
|
|
113
|
+
returns: DatedReturn[];
|
|
114
|
+
bridged: string[];
|
|
115
|
+
}
|
|
116
|
+
|
|
73
117
|
/**
|
|
74
118
|
* Restates daily USD returns in the account currency at the FX closes of each
|
|
75
119
|
* return's own start and end dates: the holding's USD value is divided by the
|
|
@@ -77,23 +121,29 @@ export function accountFxReason(currency: string, problem: string): string {
|
|
|
77
121
|
* (1 + r) * fx(start) / fx(end) - 1. A listing already converted from its own
|
|
78
122
|
* currency to USD therefore converts at the cross of the two closes of the
|
|
79
123
|
* same date. Returns before the first FX close or after the last are dropped,
|
|
80
|
-
* so the series starts and ends with the FX history
|
|
81
|
-
*
|
|
124
|
+
* so the series starts and ends with the FX history. An endpoint without an FX
|
|
125
|
+
* close converts at the last close before it across a gap of up to
|
|
126
|
+
* MAX_BRIDGED_FX_DAYS FX trading days (Good Friday and Easter Monday), and
|
|
127
|
+
* says so in `bridged`; a longer gap rejects the whole series.
|
|
82
128
|
*/
|
|
83
129
|
export function convertReturnsToAccount(
|
|
84
130
|
returns: readonly DatedReturn[],
|
|
85
131
|
fx: Pick<AccountFx, "currency" | "closes" | "first" | "asOf">,
|
|
86
|
-
):
|
|
132
|
+
): AccountReturns {
|
|
87
133
|
if (!fx.first || !fx.asOf) throw new Error(accountFxReason(fx.currency, FX_UNAVAILABLE));
|
|
88
134
|
const converted: DatedReturn[] = [];
|
|
135
|
+
const bridged = new Set<string>();
|
|
136
|
+
const rateOn = (date: string) => fx.closes[date];
|
|
89
137
|
for (const point of returns) {
|
|
90
138
|
if (point.startDateKey < fx.first || point.dateKey > fx.asOf) continue;
|
|
91
|
-
const start =
|
|
92
|
-
end =
|
|
93
|
-
if (!
|
|
94
|
-
|
|
139
|
+
const start = bridgedFxClose(rateOn, point.startDateKey),
|
|
140
|
+
end = bridgedFxClose(rateOn, point.dateKey);
|
|
141
|
+
if (!start || !end) throw new Error(accountFxReason(fx.currency, FX_GAP));
|
|
142
|
+
if (start.bridged) bridged.add(point.startDateKey);
|
|
143
|
+
if (end.bridged) bridged.add(point.dateKey);
|
|
144
|
+
converted.push({ ...point, value: (1 + point.value) * (start.rate / end.rate) - 1 });
|
|
95
145
|
}
|
|
96
|
-
return converted;
|
|
146
|
+
return { returns: converted, bridged: [...bridged].sort() };
|
|
97
147
|
}
|
|
98
148
|
|
|
99
149
|
/** The pair that restates a listing currency in USD, or null when there is none. */
|
|
@@ -127,7 +177,9 @@ export function validateFxHistory(
|
|
|
127
177
|
* On a US session the listing did not trade (its own holiday), the holding is
|
|
128
178
|
* worth its last local close at that day's FX close: its USD value moves with
|
|
129
179
|
* the currency alone, as it would in the account. A US session with no FX
|
|
130
|
-
* close
|
|
180
|
+
* close converts at the last close before it across a gap of up to
|
|
181
|
+
* MAX_BRIDGED_FX_DAYS FX trading days, listed in `bridged`; a longer FX gap,
|
|
182
|
+
* or a local silence longer than any market holiday, rejects the whole
|
|
131
183
|
* series instead of bridging it. The series ends at the earlier of the last
|
|
132
184
|
* local close and the last FX close, so no close is carried past the data.
|
|
133
185
|
*/
|
|
@@ -135,7 +187,7 @@ export function convertClosesToUsd(
|
|
|
135
187
|
local: readonly { date: string; close: number }[],
|
|
136
188
|
fx: FxCloses,
|
|
137
189
|
divisor: number,
|
|
138
|
-
): PricePoint[] {
|
|
190
|
+
): { points: PricePoint[]; bridged: string[] } {
|
|
139
191
|
const points = [...local]
|
|
140
192
|
.map((point) => ({ date: point.date.slice(0, 10), close: point.close }))
|
|
141
193
|
.sort((left, right) => left.date.localeCompare(right.date));
|
|
@@ -143,6 +195,8 @@ export function convertClosesToUsd(
|
|
|
143
195
|
const start = points[0]!.date > fx.first ? points[0]!.date : fx.first;
|
|
144
196
|
const end = points.at(-1)!.date < fx.asOf ? points.at(-1)!.date : fx.asOf;
|
|
145
197
|
const converted: PricePoint[] = [];
|
|
198
|
+
const bridged: string[] = [];
|
|
199
|
+
const rateOn = (date: string) => fx.closes.get(date);
|
|
146
200
|
let cursor = 0;
|
|
147
201
|
for (let time = Date.parse(`${start}T00:00:00Z`); time <= Date.parse(`${end}T00:00:00Z`); time += DAY_MS) {
|
|
148
202
|
const date = new Date(time).toISOString().slice(0, 10);
|
|
@@ -153,11 +207,12 @@ export function convertClosesToUsd(
|
|
|
153
207
|
const last = points[cursor]!;
|
|
154
208
|
if (last.date > date) continue;
|
|
155
209
|
if (time - Date.parse(`${last.date}T00:00:00Z`) > MAX_LOCAL_CLOSED_DAYS * DAY_MS) throw new Error(LOCAL_GAP);
|
|
156
|
-
const
|
|
157
|
-
if (
|
|
158
|
-
|
|
210
|
+
const fxClose = bridgedFxClose(rateOn, date);
|
|
211
|
+
if (!fxClose) throw new Error(FX_GAP);
|
|
212
|
+
if (fxClose.bridged) bridged.push(date);
|
|
213
|
+
const close = (last.close * fxClose.rate) / divisor;
|
|
159
214
|
converted.push({ date: new Date(time), open: close, high: close, low: close, close });
|
|
160
215
|
}
|
|
161
216
|
if (converted.length < 2) throw new Error("Daily history unavailable");
|
|
162
|
-
return converted;
|
|
217
|
+
return { points: converted, bridged };
|
|
163
218
|
}
|
|
@@ -3,6 +3,7 @@ import type { HeadlessPaneDefinition } from "../../../types/plugin";
|
|
|
3
3
|
import { fetchPortfolioRiskMarket, RISK_HISTORY_LIMIT } from "./risk-client";
|
|
4
4
|
import { parsePortfolioRiskEvidence } from "./risk-evidence";
|
|
5
5
|
import { describeCollections } from "../../../portfolio/collection-name";
|
|
6
|
+
import { shellWord } from "../../../utils/shell-word";
|
|
6
7
|
import { formatShocks, parseAssetShifts, parseFxShifts, riskCashLines } from "./risk-book";
|
|
7
8
|
import {
|
|
8
9
|
BASKET_VIEWS,
|
|
@@ -48,6 +49,22 @@ function reportPortfolioName(metadata: Record<string, unknown> | undefined): str
|
|
|
48
49
|
return typeof name === "string" ? name : undefined;
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
/**
|
|
53
|
+
* What a portfolio with nothing to estimate says, as one line: the commands
|
|
54
|
+
* that fill it. Risk is read from lots, so a symbol listed without shares
|
|
55
|
+
* (`portfolio add`) is no holding yet; `portfolio position set` gives it
|
|
56
|
+
* shares, and adds the symbol too when it is not listed.
|
|
57
|
+
*/
|
|
58
|
+
function emptyPortfolioHint(name: string, listed: readonly string[]): string {
|
|
59
|
+
const portfolio = shellWord(name);
|
|
60
|
+
if (listed.length > 0) {
|
|
61
|
+
return `${name} lists ${listed.slice(0, 3).join(", ")}${listed.length > 3 ? ` and ${listed.length - 3} more` : ""} but holds no shares: `
|
|
62
|
+
+ `gloom portfolio position set ${portfolio} ${shellWord(listed[0]!)} 10 150.`;
|
|
63
|
+
}
|
|
64
|
+
return `No holdings in ${name}. Add symbols with gloom portfolio add ${portfolio} AAPL,MSFT, `
|
|
65
|
+
+ `then their shares with gloom portfolio position set ${portfolio} AAPL 10 150.`;
|
|
66
|
+
}
|
|
67
|
+
|
|
51
68
|
/** Rows of one view in the compact result; the pane and reports keep every row. */
|
|
52
69
|
const COMPACT_ROW_LIMIT = 100;
|
|
53
70
|
/** The correlation view is every pair of holdings; the compact result keeps the strongest. */
|
|
@@ -260,6 +277,16 @@ export const portfolioRiskHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
260
277
|
throw new Error(
|
|
261
278
|
"Imported evidence belongs to a different portfolio or currency",
|
|
262
279
|
);
|
|
280
|
+
// A portfolio with no holdings and no cash has nothing to estimate: say how to fill it, not a table of dashes.
|
|
281
|
+
if (tickers.length === 0 && cash.length === 0 && !evidence) {
|
|
282
|
+
const listed = local.tickers.filter((ticker) => ticker.metadata.portfolios.includes(id)).map((ticker) => ticker.metadata.ticker);
|
|
283
|
+
return {
|
|
284
|
+
sections: [],
|
|
285
|
+
complete: false,
|
|
286
|
+
empty: emptyPortfolioHint(local.portfolio.name, listed),
|
|
287
|
+
metadata: { portfolio: { id, name: local.portfolio.name, currency: local.portfolio.currency } },
|
|
288
|
+
};
|
|
289
|
+
}
|
|
263
290
|
const requestedView = isRiskView(args.options.view) ? args.options.view : null;
|
|
264
291
|
const view: string = requestedView ?? "risk";
|
|
265
292
|
const localOnly =
|
|
@@ -48,7 +48,16 @@ import {
|
|
|
48
48
|
type RiskHoldingRequest,
|
|
49
49
|
type RiskMarketSnapshot,
|
|
50
50
|
} from "./risk-client";
|
|
51
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
accountFxPair,
|
|
53
|
+
accountFxReason,
|
|
54
|
+
bridgedFxClose,
|
|
55
|
+
convertReturnsToAccount,
|
|
56
|
+
FX_UNAVAILABLE,
|
|
57
|
+
fxBridgeNote,
|
|
58
|
+
riskConversion,
|
|
59
|
+
type AccountFx,
|
|
60
|
+
} from "./risk-fx";
|
|
52
61
|
|
|
53
62
|
interface PortfolioRiskHolding {
|
|
54
63
|
id: string;
|
|
@@ -77,6 +86,8 @@ interface PortfolioRiskHolding {
|
|
|
77
86
|
historyAsOf: string | null;
|
|
78
87
|
/** Daily returns in the account currency. */
|
|
79
88
|
returns: DatedReturn[];
|
|
89
|
+
/** Dates its returns converted at the FX close before them, by pair ("GBP/USD"), its daily closes having none (bridgedFxClose). */
|
|
90
|
+
fxBridged: ReadonlyMap<string, readonly string[]>;
|
|
80
91
|
error: string | null;
|
|
81
92
|
/** Why the holding is outside the basket; null when it qualifies. */
|
|
82
93
|
leftOut: string | null;
|
|
@@ -386,15 +397,43 @@ function cashReason(line: RiskCashLine, accountCurrency: string): string {
|
|
|
386
397
|
? "Cash is outside the equity basket"
|
|
387
398
|
: `Cash is outside the equity basket; its ${accountCurrency} value moves with ${own}`;
|
|
388
399
|
}
|
|
400
|
+
/** One note per pair whose missing daily closes the basket's returns were converted across (fxBridgeNote). */
|
|
401
|
+
function fxBridgeNotes(members: readonly Pick<PortfolioRiskHolding, "fxBridged">[]): string[] {
|
|
402
|
+
const byPair = new Map<string, Set<string>>();
|
|
403
|
+
for (const member of members)
|
|
404
|
+
for (const [pair, dates] of member.fxBridged) {
|
|
405
|
+
const set = byPair.get(pair) ?? new Set<string>();
|
|
406
|
+
for (const date of dates) set.add(date);
|
|
407
|
+
byPair.set(pair, set);
|
|
408
|
+
}
|
|
409
|
+
return [...byPair].flatMap(([pair, dates]) => fxBridgeNote(pair, dates) ?? []);
|
|
410
|
+
}
|
|
389
411
|
/** USD returns in the account currency, or none with the reason its daily closes cannot convert them. */
|
|
390
|
-
function accountReturns(
|
|
391
|
-
|
|
412
|
+
function accountReturns(
|
|
413
|
+
returns: readonly DatedReturn[],
|
|
414
|
+
account: AccountFx,
|
|
415
|
+
): { returns: DatedReturn[]; bridged: string[]; error: string | null } {
|
|
416
|
+
if (account.error) return { returns: [], bridged: [], error: account.error };
|
|
392
417
|
try {
|
|
393
|
-
return {
|
|
418
|
+
return { ...convertReturnsToAccount(returns, account), error: null };
|
|
394
419
|
} catch (error) {
|
|
395
|
-
return { returns: [], error: error instanceof Error ? error.message : accountFxReason(account.currency, FX_UNAVAILABLE) };
|
|
420
|
+
return { returns: [], bridged: [], error: error instanceof Error ? error.message : accountFxReason(account.currency, FX_UNAVAILABLE) };
|
|
396
421
|
}
|
|
397
422
|
}
|
|
423
|
+
/** The dates a holding's returns converted at the FX close before them, by pair: its listing's into USD, then USD into the account's. */
|
|
424
|
+
function holdingFxBridges(
|
|
425
|
+
listingCurrency: string | null,
|
|
426
|
+
listingBridged: readonly string[] | undefined,
|
|
427
|
+
account: AccountFx | null | undefined,
|
|
428
|
+
accountBridged: readonly string[] | undefined,
|
|
429
|
+
): Map<string, readonly string[]> {
|
|
430
|
+
const bridges = new Map<string, readonly string[]>();
|
|
431
|
+
const listingPair = listingCurrency && listingBridged?.length ? accountFxPair(riskConversion(listingCurrency)?.currency ?? listingCurrency) : null;
|
|
432
|
+
if (listingPair) bridges.set(listingPair, listingBridged!);
|
|
433
|
+
const accountPair = account?.pair;
|
|
434
|
+
if (accountPair && accountBridged?.length) bridges.set(accountPair, [...(bridges.get(accountPair) ?? []), ...accountBridged]);
|
|
435
|
+
return bridges;
|
|
436
|
+
}
|
|
398
437
|
/**
|
|
399
438
|
* "returns in TRY at daily USD/TRY closes": the currency the basket views of a
|
|
400
439
|
* non-USD account are in and how they got there; null for a USD account, or
|
|
@@ -474,6 +513,8 @@ export function riskCoverageNotices(coverage: RiskCoverage | undefined): string[
|
|
|
474
513
|
`${row.symbol} (${row.share == null ? "value unknown" : `${shareText(row.share)} of the book`}) is excluded from the risk estimate: ${lowerFirst(row.reason.replace(/\.$/, ""))}.`,
|
|
475
514
|
);
|
|
476
515
|
}
|
|
516
|
+
/** What an empty portfolio's pane says after "No holdings in this portfolio.": where holdings come from. */
|
|
517
|
+
export const NO_HOLDINGS_HINT = "Add holdings from the Portfolio pane (PF), or connect a broker in BR to sync them.";
|
|
477
518
|
/** Why the basket views show no estimate, when too little of the holdings qualifies. */
|
|
478
519
|
export function riskCoverageShortfall(
|
|
479
520
|
coverage: RiskCoverage | undefined,
|
|
@@ -690,6 +731,7 @@ export function buildPortfolioRisk(
|
|
|
690
731
|
// Converted returns end with the account currency's last daily close.
|
|
691
732
|
historyAsOf: (inAccount ? inAccount.returns.at(-1)?.dateKey : null) ?? history?.asOf ?? null,
|
|
692
733
|
returns,
|
|
734
|
+
fxBridged: holdingFxBridges(convertedFrom, history?.converted?.fxBridged, account, inAccount?.bridged),
|
|
693
735
|
error,
|
|
694
736
|
leftOut,
|
|
695
737
|
};
|
|
@@ -774,6 +816,8 @@ export function buildPortfolioRisk(
|
|
|
774
816
|
"Converted holdings are matched to the US close of the same date; where their market closes at another time, correlations and betas to US holdings read lower than they are.",
|
|
775
817
|
]
|
|
776
818
|
: []),
|
|
819
|
+
// A pair's daily closes skip Good Friday, Easter Monday, 25 Dec and 1 Jan; returns across them use the close before.
|
|
820
|
+
...fxBridgeNotes(members),
|
|
777
821
|
];
|
|
778
822
|
const warnings = [
|
|
779
823
|
...market.warnings,
|
|
@@ -796,12 +840,14 @@ export function buildPortfolioRisk(
|
|
|
796
840
|
return (instrument && byId.get(riskInstrumentId(instrument))?.returns) || [];
|
|
797
841
|
};
|
|
798
842
|
// SPY's USD returns are the market factor and the equity stress; the risk
|
|
799
|
-
// rows compare the basket with SPY in the basket's own currency
|
|
800
|
-
//
|
|
801
|
-
//
|
|
843
|
+
// rows compare the basket with SPY in the basket's own currency, bridged
|
|
844
|
+
// across missing account closes as the holdings are (bridgedFxClose). A
|
|
845
|
+
// session past a longer gap is a gap in that benchmark, so it cannot blank
|
|
846
|
+
// the holdings the closes do cover.
|
|
802
847
|
const spy = proxy(RISK_BENCHMARK);
|
|
848
|
+
const accountClose = (date: string) => account ? bridgedFxClose((day) => account.closes[day], date) : null;
|
|
803
849
|
const benchmark = account
|
|
804
|
-
? accountReturns(spy.filter((point) =>
|
|
850
|
+
? accountReturns(spy.filter((point) => accountClose(point.startDateKey) && accountClose(point.dateKey)), account).returns
|
|
805
851
|
: spy;
|
|
806
852
|
// Since the filing only for a portfolio that has one; any other keeps the trailing window and says why.
|
|
807
853
|
const filed = window.since === "filed" ? portfolio.importedFrom?.filed ?? null : null;
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
useAsyncResource,
|
|
31
31
|
useAutoRefresh,
|
|
32
32
|
usePaneSettingValue,
|
|
33
|
+
usePluginAppActions,
|
|
33
34
|
usePluginPaneState,
|
|
34
35
|
useShortcut,
|
|
35
36
|
} from "../../../public/react";
|
|
@@ -54,6 +55,7 @@ import {
|
|
|
54
55
|
HOLDINGS_SHARE_UNIT,
|
|
55
56
|
HOLDINGS_SUMMARY_ROW_IDS,
|
|
56
57
|
isShockRow,
|
|
58
|
+
NO_HOLDINGS_HINT,
|
|
57
59
|
portfolioRiskRequests,
|
|
58
60
|
portfolioRiskTickers,
|
|
59
61
|
riskCoverageLine,
|
|
@@ -234,6 +236,7 @@ function RiskDetail({
|
|
|
234
236
|
export function PortfolioRiskPane({ focused, width, height }: PaneProps) {
|
|
235
237
|
const instance = usePaneInstance(),
|
|
236
238
|
renderer = useRendererHost();
|
|
239
|
+
const { showPane } = usePluginAppActions();
|
|
237
240
|
const portfolios = useAppSelector((state) => state.config.portfolios),
|
|
238
241
|
tickers = useAppSelector((state) => state.tickers),
|
|
239
242
|
brokerAccounts = useAppSelector((state) => state.brokerAccounts);
|
|
@@ -403,14 +406,16 @@ export function PortfolioRiskPane({ focused, width, height }: PaneProps) {
|
|
|
403
406
|
const model = derived.model;
|
|
404
407
|
const marketLoading = resource.loading && !resource.data;
|
|
405
408
|
// A basket view with nothing to estimate says why in the body, not with a table of dashes.
|
|
406
|
-
const basketBody = useMemo<{ loading?: boolean; error?: string; title?: string; message?: string } | null>(() => {
|
|
409
|
+
const basketBody = useMemo<{ loading?: boolean; error?: string; title?: string; message?: string; noHoldings?: boolean } | null>(() => {
|
|
407
410
|
if (!model || !BASKET_VIEWS.has(view)) return null;
|
|
408
411
|
if (!frozen && !resource.data)
|
|
409
412
|
return marketLoading ? { loading: true } : resource.error ? { error: resource.error } : null;
|
|
410
413
|
if (view === "holdings") return null;
|
|
411
414
|
const shortfall = riskCoverageShortfall(model.coverage);
|
|
412
415
|
// The currency and price shocks reprice the book without the basket, so they stay; the footer says why the basket scenarios do not.
|
|
413
|
-
|
|
416
|
+
if (shortfall && view === "stress" && model.rows.stress.some(isShockRow)) return null;
|
|
417
|
+
// A portfolio with no holdings says where they come from, as the other panes' empty states do.
|
|
418
|
+
return shortfall && !model.coverage?.holdings ? { ...shortfall, message: NO_HOLDINGS_HINT, noHoldings: true } : shortfall;
|
|
414
419
|
}, [frozen, marketLoading, model, resource.data, resource.error, view]);
|
|
415
420
|
// Stress with too little covered for the basket scenarios, its shocks shown alone.
|
|
416
421
|
const stressShortfall = useMemo(
|
|
@@ -816,6 +821,7 @@ export function PortfolioRiskPane({ focused, width, height }: PaneProps) {
|
|
|
816
821
|
loadingLabel="Loading portfolio risk"
|
|
817
822
|
emptyTitle={basketBody.title}
|
|
818
823
|
emptyMessage={basketBody.message || undefined}
|
|
824
|
+
actions={basketBody.noHoldings ? <Button label="Open Portfolio" compact onPress={() => showPane("portfolio-list")} /> : undefined}
|
|
819
825
|
/>
|
|
820
826
|
) : evidenceMissing ? (
|
|
821
827
|
<PaneStatusBody
|
|
@@ -5,11 +5,12 @@ import { attentionHeadless } from "./headless";
|
|
|
5
5
|
import { TrendingPane } from "./trending";
|
|
6
6
|
import { AttentionPane } from "./pane";
|
|
7
7
|
import { ATTENTION_SERIES } from "./series";
|
|
8
|
+
import { ATTENTION_LISTED } from "./listing";
|
|
8
9
|
export const attentionModule: PluginModule = {
|
|
9
10
|
panes: [{ id: "attention", name: "Research Attention", icon: "A", component: AttentionPane, defaultPosition: "right", defaultMode: "floating",
|
|
10
11
|
defaultFloatingSize: { width: 126, height: 32 }, tableExport: true, headless: attentionHeadless },
|
|
11
12
|
{ id: "attention-trending", reportFreshness: { status: "not-a-feed", basis: "hourly publication" }, name: "Gloom Trending", icon: "A", component: TrendingPane, defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 58, height: 12 } }],
|
|
12
|
-
paneTemplates: [{ id: "attention-pane", paneId: "attention", label: "Research Attention",
|
|
13
|
+
paneTemplates: [{ id: "attention-pane", paneId: "attention", label: "Research Attention", listed: ATTENTION_LISTED,
|
|
13
14
|
description: "Pro research attention: rankings, abnormal activity, sectors, countries and privacy-qualified hourly history.",
|
|
14
15
|
keywords: ["attention", "trending", "research", "activity"], shortcut: { prefix: "ATTN", argKind: "text", argPlaceholder: "TICKER", argOptional: true },
|
|
15
16
|
headless: attentionHeadless, createInstance: (_ctx, options) => ({ placement: "floating", settings: { window: attentionWindow(options?.values?.window), symbol: (options?.arg ?? options?.symbol)?.trim().toUpperCase() ?? "" } }) },
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether ATTN is listed: in command-bar search, Help, `gloom catalog` and
|
|
3
|
+
* search, the plugin directory and chat suggestions, and its two screener
|
|
4
|
+
* fields (ATTN HRS, ATTN Z) in the EQS field and metric pickers.
|
|
5
|
+
*
|
|
6
|
+
* Research attention publishes an hour only once enough opted-in users
|
|
7
|
+
* researched in it, and it has published nothing for weeks, so ATTN stays
|
|
8
|
+
* unlisted until it does. Typing ATTN still opens it, a screen that already
|
|
9
|
+
* uses its fields keeps them, and the Attention Counts setting and its
|
|
10
|
+
* collection are unchanged. Set it to true the day ATTN publishes again,
|
|
11
|
+
* with the server's own switch, and refresh the docs snapshot
|
|
12
|
+
* (`bun run docs:snapshot`) once the site lists ATTN.
|
|
13
|
+
*/
|
|
14
|
+
export const ATTENTION_LISTED = false;
|
|
@@ -3,7 +3,7 @@ import type { InstrumentRef } from "../../../market-data/request-types";
|
|
|
3
3
|
import { DATE_OPTION_EXAMPLES, isoDateOption } from "../../../utils/calendar-date";
|
|
4
4
|
import { resolveHeadlessInstrument } from "../shared/headless-market-data";
|
|
5
5
|
import { createBacktestDependencies, loadBacktestHistory } from "./client";
|
|
6
|
-
import { BACKTEST_PRESETS, DEFAULT_PRESET, lookbackYears, resolveRules } from "./presets";
|
|
6
|
+
import { BACKTEST_PRESETS, CUSTOM_PRESET_ID, DEFAULT_PRESET, lookbackYears, presetRulesConflict, resolveRules } from "./presets";
|
|
7
7
|
import { BACKTEST_FRESHNESS, backtestPriceUnit, backtestReport } from "./report";
|
|
8
8
|
import { parseRule, RULE_SYNTAX } from "./rules";
|
|
9
9
|
import { runHistoryBacktest } from "./window";
|
|
@@ -37,8 +37,10 @@ export const backtestHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
37
37
|
],
|
|
38
38
|
},
|
|
39
39
|
options: [
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
// No default: a run given --entry or --exit and no preset tests those rules, and one given both is refused.
|
|
41
|
+
{ key: "preset", type: "enum",
|
|
42
|
+
description: `Strategy preset, ${DEFAULT_PRESET.id} when neither it nor a rule is given; --entry and --exit alone (or with custom) test your own rules`,
|
|
43
|
+
values: [...BACKTEST_PRESETS.map(({ id }) => ({ value: id })), { value: CUSTOM_PRESET_ID }] },
|
|
42
44
|
{ key: "entry", type: "string", defaultValue: "", description: `Custom entry rule, for example 'close > sma(200)' or 'weekday == 3'. ${RULE_SYNTAX}` },
|
|
43
45
|
{ key: "exit", type: "string", defaultValue: "", description: "Custom exit rule, for example 'close < sma(200)' or 'weekday == 4'. Same operands and comparisons as --entry" },
|
|
44
46
|
{ key: "lookback", type: "enum", defaultValue: "10", values: [{ value: "5" }, { value: "10" }, { value: "max" }], description: "Evaluation years after indicator warmup" },
|
|
@@ -55,8 +57,11 @@ export const backtestHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
55
57
|
pluginState: { pluginId: "ticker-research", key: "backtest:view" } },
|
|
56
58
|
],
|
|
57
59
|
async load(args, ctx) {
|
|
58
|
-
const preset =
|
|
59
|
-
const
|
|
60
|
+
const preset = args.options.preset == null ? null : String(args.options.preset);
|
|
61
|
+
const typedEntry = String(args.options.entry ?? ""), typedExit = String(args.options.exit ?? "");
|
|
62
|
+
const conflict = presetRulesConflict(preset, typedEntry, typedExit);
|
|
63
|
+
if (conflict) throw new Error(conflict);
|
|
64
|
+
const rules = resolveRules(preset, typedEntry, typedExit);
|
|
60
65
|
const entry = parseRule(rules.entry), exit = parseRule(rules.exit);
|
|
61
66
|
const instrument = await resolveHeadlessInstrument(ctx, args.symbols[0]!);
|
|
62
67
|
const [history, unit] = await Promise.all([
|
|
@@ -30,7 +30,7 @@ import { loadBacktestHistory } from "./client";
|
|
|
30
30
|
import type { BacktestTrade } from "./engine";
|
|
31
31
|
import { backtestEquityCsv } from "./export";
|
|
32
32
|
import { formatStat, PRICE_RETURN_NOTE, SUMMARY_ROWS, type SummaryRow } from "./format";
|
|
33
|
-
import { BACKTEST_PRESETS, DEFAULT_PRESET, lookbackYears, resolveRules } from "./presets";
|
|
33
|
+
import { BACKTEST_PRESETS, DEFAULT_PRESET, lookbackYears, resolveRules, strategyPreset } from "./presets";
|
|
34
34
|
import { backtestPriceUnit, backtestReport } from "./report";
|
|
35
35
|
import { parseRule } from "./rules";
|
|
36
36
|
import { continuationNote, runHistoryBacktest, type HistoryBacktestResult } from "./window";
|
|
@@ -82,9 +82,14 @@ export function BacktestPane({ width, height, focused }: PaneProps) {
|
|
|
82
82
|
const paneId = usePaneInstanceId();
|
|
83
83
|
const { openPaneSettings } = usePluginAppActions();
|
|
84
84
|
const { symbol, ticker, financials, error: identityError } = usePaneTicker();
|
|
85
|
-
|
|
86
|
-
const [
|
|
87
|
-
const [
|
|
85
|
+
// Rules typed in settings, or by `shot BT --entry ... --exit ...`, with no strategy chosen are tested as custom rules.
|
|
86
|
+
const [chosenPreset, setPreset] = usePaneSettingValue<string | null>("preset", null);
|
|
87
|
+
const [typedEntry] = usePaneSettingValue<string | null>("entry", null);
|
|
88
|
+
const [typedExit] = usePaneSettingValue<string | null>("exit", null);
|
|
89
|
+
const preset = strategyPreset(chosenPreset, typedEntry ?? "", typedExit ?? "");
|
|
90
|
+
// Custom starts from the default preset's rules until one is typed.
|
|
91
|
+
const entryText = typedEntry ?? DEFAULT_PRESET.entry;
|
|
92
|
+
const exitText = typedExit ?? DEFAULT_PRESET.exit;
|
|
88
93
|
const [lookback, setLookback] = usePaneSettingValue("lookback", "10");
|
|
89
94
|
const [costText] = usePaneSettingValue<string | number>("cost", "5");
|
|
90
95
|
// Set by `shot BT --end` and `--include-partial`; the pane has no control for them.
|
|
@@ -26,9 +26,35 @@ export const LOOKBACK_OPTIONS = [
|
|
|
26
26
|
{ value: "max", label: "All history" },
|
|
27
27
|
];
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
export const CUSTOM_PRESET_ID = "custom";
|
|
30
|
+
|
|
31
|
+
const typedRule = (entry: string, exit: string) => entry.trim() !== "" || exit.trim() !== "";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The strategy a run tests: the preset chosen; with none chosen, the typed
|
|
35
|
+
* rules when there are any (`fn BT --entry ... --exit ...` means custom),
|
|
36
|
+
* else the default preset.
|
|
37
|
+
*/
|
|
38
|
+
export function strategyPreset(preset: string | null | undefined, entry: string, exit: string): string {
|
|
39
|
+
if (preset) return preset;
|
|
40
|
+
return typedRule(entry, exit) ? CUSTOM_PRESET_ID : DEFAULT_PRESET.id;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A preset's rules, or the typed ones for "custom" and for rules typed with no preset chosen. */
|
|
44
|
+
export function resolveRules(preset: string | null | undefined, entry: string, exit: string): { entry: string; exit: string; label: string } {
|
|
45
|
+
const id = strategyPreset(preset, entry, exit);
|
|
46
|
+
const match = BACKTEST_PRESETS.find((candidate) => candidate.id === id);
|
|
32
47
|
return match ? { entry: match.entry, exit: match.exit, label: match.label } : { entry, exit, label: "Custom rules" };
|
|
33
48
|
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Why rules typed beside a preset cannot both be tested (`--preset
|
|
52
|
+
* golden-cross --entry ...`), naming both; null when they agree.
|
|
53
|
+
*/
|
|
54
|
+
export function presetRulesConflict(preset: string | null | undefined, entry: string, exit: string): string | null {
|
|
55
|
+
if (!preset || preset === CUSTOM_PRESET_ID || !typedRule(entry, exit)) return null;
|
|
56
|
+
const typed = [entry.trim() ? "--entry" : null, exit.trim() ? "--exit" : null].filter(Boolean).join(" and ");
|
|
57
|
+
return `${typed} ${entry.trim() && exit.trim() ? "are custom rules" : "is a custom rule"}, but --preset ${preset} tests its own. `
|
|
58
|
+
+ `Leave out --preset to test the typed rules, or give --preset ${CUSTOM_PRESET_ID}.`;
|
|
59
|
+
}
|
|
34
60
|
export const lookbackYears = (value: unknown) => value === "max" ? null : value === "5" ? 5 : 10;
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
resetSignedInBrokerCatalog,
|
|
12
12
|
subscribeSignedInBrokers,
|
|
13
13
|
} from "../../../brokers/signed-in/catalog";
|
|
14
|
-
import { DataTableStackView, EmptyState } from "../../../components";
|
|
14
|
+
import { Button, DataTableStackView, EmptyState } from "../../../components";
|
|
15
15
|
import { t } from "../../../i18n";
|
|
16
16
|
import { useAppLanguage } from "../../../i18n/react";
|
|
17
17
|
import {
|
|
@@ -50,6 +50,7 @@ export function BrokersPane({ focused, width, height }: PaneProps) {
|
|
|
50
50
|
const config = usePaneAppConfig();
|
|
51
51
|
const paneId = usePaneInstanceId();
|
|
52
52
|
const brokerAccounts = useAppSelector((state) => state.brokerAccounts);
|
|
53
|
+
const brokerSyncErrors = useAppSelector((state) => state.brokerSyncErrors);
|
|
53
54
|
const { getBrokerAdapter, getBrokerLogo } = usePluginBrokerActions();
|
|
54
55
|
const desktop = useUiCapabilities().nativePaneChrome === true;
|
|
55
56
|
// The profile is remembered by id so a reload lands on the same row.
|
|
@@ -82,8 +83,8 @@ export function BrokersPane({ focused, width, height }: PaneProps) {
|
|
|
82
83
|
const signedInBrokers = useSyncExternalStore(subscribeSignedInBrokers, getSignedInBrokers, getSignedInBrokers);
|
|
83
84
|
const { ends: signInEnds, reload: reloadSignInEnds } = useSignInEnds(config.brokerInstances);
|
|
84
85
|
const rows = useMemo(
|
|
85
|
-
() => buildBrokerProfileRows(config, adapters, brokerAccounts, signInEnds, getBrokerLogo),
|
|
86
|
-
[adapters, brokerAccounts, config, getBrokerLogo, language, signInEnds, signedInBrokers, statusVersion],
|
|
86
|
+
() => buildBrokerProfileRows(config, adapters, brokerAccounts, signInEnds, getBrokerLogo, brokerSyncErrors),
|
|
87
|
+
[adapters, brokerAccounts, brokerSyncErrors, config, getBrokerLogo, language, signInEnds, signedInBrokers, statusVersion],
|
|
87
88
|
);
|
|
88
89
|
const selectedIndex = Math.max(0, rows.findIndex((row) => row.id === selectedId));
|
|
89
90
|
const selectedRow = rows[selectedIndex] ?? null;
|
|
@@ -339,10 +340,14 @@ export function BrokersPane({ focused, width, height }: PaneProps) {
|
|
|
339
340
|
sortDirection="asc"
|
|
340
341
|
getItemKey={(row) => row.id}
|
|
341
342
|
renderCell={renderCell}
|
|
342
|
-
// The footer's a
|
|
343
|
+
// The same add the footer's a and the command bar start; as the state's first
|
|
344
|
+
// action it also answers Enter.
|
|
343
345
|
emptyContent={(
|
|
344
346
|
<Box width="100%" paddingX={1} paddingY={1}>
|
|
345
|
-
<EmptyState
|
|
347
|
+
<EmptyState
|
|
348
|
+
title={t("No broker profiles.")}
|
|
349
|
+
actions={<Button label="Add Broker Account" onPress={startAdd} />}
|
|
350
|
+
/>
|
|
346
351
|
</Box>
|
|
347
352
|
)}
|
|
348
353
|
emptyStateTitle={t("No broker profiles.")}
|
|
@@ -125,6 +125,7 @@ export function buildBrokerProfileRows(
|
|
|
125
125
|
brokerAccounts: Record<string, BrokerAccount[]>,
|
|
126
126
|
signInEnds: SignInEnds = new Map(),
|
|
127
127
|
pluginLogo: BrokerPluginLogo = () => null,
|
|
128
|
+
syncErrors: Readonly<Record<string, string>> = {},
|
|
128
129
|
): BrokerProfileRow[] {
|
|
129
130
|
return config.brokerInstances.map((instance) => {
|
|
130
131
|
const adapter = adapters.get(instance.brokerType) ?? null;
|
|
@@ -132,7 +133,11 @@ export function buildBrokerProfileRows(
|
|
|
132
133
|
// An adapter that fronts several brokers names the broker and how it connects.
|
|
133
134
|
const described = adapter?.describeInstance?.(instance) ?? null;
|
|
134
135
|
const mode = described?.method ?? resolveMode(adapter, instance, status);
|
|
135
|
-
const
|
|
136
|
+
const connection = resolveState(instance, adapter, mode, status);
|
|
137
|
+
const syncError = syncErrors[instance.id];
|
|
138
|
+
const state = syncError && !["disabled", "unavailable", "sign-in"].includes(connection.state)
|
|
139
|
+
? { ...connection, state: "error" as const, label: t("Sync failed"), message: t(syncError) }
|
|
140
|
+
: connection;
|
|
136
141
|
const accounts = brokerAccounts[instance.id] ?? [];
|
|
137
142
|
const portfolioLastSyncedAt = Math.max(
|
|
138
143
|
0,
|