@gloom-sh/terminal 0.18.5 → 0.19.1
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 +163 -8
- package/src/api-client/app-attention.ts +3 -0
- package/src/api-client/beneficial-owners.ts +3 -0
- package/src/api-client/company-kpis.ts +3 -0
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +19 -7
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/hiring.ts +3 -0
- package/src/api-client/index.ts +15 -0
- package/src/api-client/ipo.ts +24 -1
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +1 -1
- package/src/api-client/mna.ts +3 -0
- package/src/api-client/paths.ts +9 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/research-activity.ts +3 -2
- package/src/api-client/roadmap.ts +85 -0
- package/src/api-client/types.ts +82 -2
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +0 -2
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +2 -2
- 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/market.ts +107 -38
- package/src/cli/commands/news-search.ts +22 -0
- package/src/cli/commands/options-chain.ts +48 -7
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +147 -32
- package/src/cli/commands/search.ts +21 -1
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +87 -36
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +25 -2
- package/src/cli/glossary.ts +31 -2
- package/src/cli/history-rows.ts +57 -9
- package/src/cli/index-roots.ts +0 -7
- package/src/cli/index.ts +2 -0
- package/src/cli/listing-arg.ts +235 -60
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +4 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/discovery.ts +21 -1
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +93 -4
- package/src/cli/pane-functions/function-help.ts +20 -10
- package/src/cli/pane-functions/headless.ts +17 -7
- package/src/cli/pane-functions/index.ts +54 -16
- package/src/cli/pane-functions/market-session.ts +66 -4
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/resolver.ts +19 -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 +22 -11
- package/src/cli/saved-copy-persistence.ts +88 -0
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +4 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-surface.tsx +52 -3
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +39 -4
- 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/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/list/model.ts +5 -0
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/routes/root/runtime.ts +6 -7
- package/src/components/command-bar/selection-runtime.ts +52 -3
- package/src/components/command-bar/surface/index.tsx +2 -0
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/pane/capture-stamp.ts +2 -2
- package/src/components/layout/pane/footer/index.tsx +22 -6
- package/src/components/layout/pane/footer/model.ts +83 -15
- package/src/components/layout/pane/status-footer.ts +35 -2
- package/src/components/layout/status-widget-space.ts +9 -0
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
- 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 +8 -21
- 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 +18 -3
- package/src/components/ui/icon.tsx +4 -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/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/plugin-cache.ts +5 -3
- package/src/data/saved-copies.ts +8 -0
- 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 +103 -142
- package/src/i18n/es.ts +20 -6
- package/src/i18n/ja.ts +20 -6
- package/src/i18n/ko.ts +20 -6
- package/src/i18n/zh-cn.ts +20 -6
- package/src/i18n/zh-tw.ts +20 -6
- package/src/market-data/depositary-pair.ts +27 -3
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +16 -3
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/options-alternatives.ts +81 -4
- package/src/market-data/quotes/metadata.ts +7 -0
- package/src/news/aggregator.ts +119 -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 +17 -0
- package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +362 -80
- package/src/plugins/builtin/alerts/command.ts +25 -1
- package/src/plugins/builtin/alerts/event-rows.ts +24 -3
- package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
- package/src/plugins/builtin/alerts/fired-log.ts +73 -9
- package/src/plugins/builtin/alerts/format.ts +75 -5
- package/src/plugins/builtin/alerts/headless.ts +34 -12
- package/src/plugins/builtin/alerts/history-rows.ts +3 -2
- package/src/plugins/builtin/alerts/history.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +79 -16
- package/src/plugins/builtin/alerts/metric-command.ts +77 -4
- package/src/plugins/builtin/alerts/metric-data.ts +54 -13
- package/src/plugins/builtin/alerts/metric-model.ts +163 -14
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
- package/src/plugins/builtin/alerts/pane.tsx +61 -23
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +12 -8
- package/src/plugins/builtin/alerts/types.ts +4 -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/broker-manager/index.tsx +7 -3
- 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 +6 -2
- package/src/plugins/builtin/cds/client.ts +142 -30
- package/src/plugins/builtin/cds/headless.ts +35 -8
- package/src/plugins/builtin/cds/model.ts +145 -18
- package/src/plugins/builtin/cds/pane.tsx +16 -5
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
- package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
- package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
- package/src/plugins/builtin/chart-composer/headless.ts +6 -1
- package/src/plugins/builtin/chart-composer/index.tsx +5 -6
- package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
- package/src/plugins/builtin/chat/controller/index.ts +5 -1
- package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
- package/src/plugins/builtin/chat/controller/state.ts +25 -2
- package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
- package/src/plugins/builtin/chat/controller/utils.ts +7 -2
- package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
- package/src/plugins/builtin/chat/status-preview.ts +79 -0
- package/src/plugins/builtin/chat/status-widget.tsx +147 -10
- package/src/plugins/builtin/chat/unread-badge.ts +51 -10
- package/src/plugins/builtin/clinical-trials/client.ts +49 -4
- package/src/plugins/builtin/clinical-trials/model.ts +86 -31
- package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
- package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
- package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- 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/headless.ts +2 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +12 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/company-kpis/headless.ts +8 -3
- package/src/plugins/builtin/company-kpis/model.ts +73 -5
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/composite-plugins.ts +2 -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 +10 -10
- 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/doe/headless.ts +3 -2
- package/src/plugins/builtin/doe/pane.tsx +13 -3
- 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/data.ts +18 -6
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
- package/src/plugins/builtin/econ/calendar-model.ts +48 -1
- package/src/plugins/builtin/econ/calendar-source.ts +4 -2
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/figures.ts +48 -0
- package/src/plugins/builtin/econ/index.tsx +14 -5
- package/src/plugins/builtin/econ/print-watch.ts +3 -1
- package/src/plugins/builtin/econ/types.ts +2 -0
- package/src/plugins/builtin/econ-statistics/client.ts +11 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
- package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +39 -9
- 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 +68 -13
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- 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 +6 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
- package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
- package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -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 +62 -59
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
- package/src/plugins/builtin/help/table-cells.tsx +13 -2
- package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +22 -3
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/holders/report-rows.ts +4 -1
- package/src/plugins/builtin/insider/headless.ts +2 -2
- package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
- package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
- package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
- 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 +33 -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/headless.ts +12 -5
- package/src/plugins/builtin/macro-day/index.tsx +1 -1
- package/src/plugins/builtin/macro-day/model.ts +39 -5
- package/src/plugins/builtin/macro-day/pane.tsx +27 -19
- 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 +27 -7
- package/src/plugins/builtin/market-movers/headless.ts +34 -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 +22 -4
- 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 +2 -1
- package/src/plugins/builtin/mna/headless.ts +2 -0
- package/src/plugins/builtin/mna/pane.tsx +3 -1
- package/src/plugins/builtin/news/headless.ts +3 -1
- package/src/plugins/builtin/news/index.tsx +4 -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 +29 -5
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/table.ts +5 -2
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +17 -4
- package/src/plugins/builtin/options-positioning/client.ts +11 -0
- package/src/plugins/builtin/options-positioning/headless.ts +21 -8
- package/src/plugins/builtin/options-positioning/index.tsx +1 -1
- package/src/plugins/builtin/options-positioning/model.ts +78 -9
- package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
- 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/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +19 -22
- 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 +7 -5
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -7
- 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/index.tsx +0 -14
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
- package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/realized-vol/client.ts +13 -5
- package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
- package/src/plugins/builtin/realized-vol/headless.ts +8 -4
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/model.ts +37 -6
- package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
- 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 +40 -12
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/index.tsx +7 -3
- package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
- package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
- package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
- 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/risk-factors/data.ts +4 -2
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/roadmap/index.tsx +28 -0
- package/src/plugins/builtin/roadmap/model.ts +67 -0
- package/src/plugins/builtin/roadmap/pane.tsx +244 -0
- 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 +16 -5
- package/src/plugins/builtin/scanner/live-headless.ts +48 -5
- package/src/plugins/builtin/scanner/live-model.ts +11 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
- 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 +4 -2
- package/src/plugins/builtin/sec/index.tsx +3 -0
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
- 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/headless-market-data.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -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 +11 -2
- package/src/plugins/builtin/shared/research-cells.tsx +1 -1
- package/src/plugins/builtin/shared/series-cache.ts +4 -4
- 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 +2 -1
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/model.ts +16 -7
- package/src/plugins/builtin/themes/pane.tsx +5 -5
- package/src/plugins/builtin/thirteenf/api.ts +12 -1
- package/src/plugins/builtin/thirteenf/data.ts +7 -7
- package/src/plugins/builtin/thirteenf/headless.ts +31 -4
- 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 +11 -3
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
- 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-auctions/headless.ts +21 -12
- package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
- 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/client.ts +3 -13
- package/src/plugins/builtin/vol-surface/headless.ts +3 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +7 -4
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/builtin/yield-curve/client.ts +61 -1
- package/src/plugins/builtin/yield-curve/headless.ts +9 -6
- package/src/plugins/builtin/yield-curve/index.tsx +34 -10
- package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/host-link.ts +1 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +11 -7
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/remote/resources.ts +2 -1
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/icons.tsx +7 -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 +3 -3
- package/src/renderers/electrobun/bun/desktop/backend.ts +21 -10
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
- package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
- package/src/renderers/electrobun/bun/index.ts +26 -2
- package/src/renderers/electrobun/bun/window/create.ts +43 -4
- package/src/renderers/electrobun/bun/window/events.ts +20 -0
- package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
- package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
- package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
- package/src/renderers/electrobun/shared/app-name.ts +13 -0
- package/src/renderers/electrobun/shared/protocol.ts +12 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +5 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/ui-host.tsx +8 -0
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- 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/fred-series.ts +3 -1
- package/src/sources/gloomberb-cloud/index.ts +43 -8
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/index.ts +8 -5
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/controller.ts +4 -3
- package/src/sync/core-contributors.ts +90 -31
- 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/test-support/preload.ts +14 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name-listing.ts +53 -13
- package/src/tickers/company-name.ts +50 -4
- package/src/tickers/index-roots.ts +7 -0
- package/src/tickers/listing-fate.ts +2 -2
- package/src/tickers/listing-venues.ts +15 -3
- package/src/tickers/search/index.ts +6 -2
- package/src/tickers/search/ranking.ts +5 -1
- package/src/tickers/search/upsert.ts +13 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/tickers/ticker-changes.ts +8 -4
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +1 -1
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/fundamentals.ts +44 -1
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/resolve-context.ts +3 -1
- package/src/time-series/resolve.ts +56 -4
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +3 -2
- package/src/time-series/studies.ts +8 -2
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +2 -3
- package/src/time-series/types.ts +22 -1
- package/src/types/company-from.ts +40 -0
- package/src/types/data-provider.ts +7 -0
- package/src/types/financials.ts +59 -1
- package/src/types/headless.ts +27 -3
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +9 -0
- package/src/ui/host.tsx +3 -0
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +165 -7
- package/src/utils/declared-dividends.ts +1 -1
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +60 -8
- 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/price-history.ts +7 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/version.ts +2 -2
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { metricFormInput, metricShortcut, parseMetricAlert } from "./metric-command";
|
|
2
|
-
import type
|
|
2
|
+
import { isRelativeMetric, type AlertMetric } from "./metric-model";
|
|
3
3
|
import type { AlertCondition } from "./types";
|
|
4
4
|
import { readAlertPrice } from "./price";
|
|
5
5
|
import { normalizeAlertSymbol } from "./quotes";
|
|
@@ -86,3 +86,27 @@ export function parseAlertCommandValues(
|
|
|
86
86
|
if (!symbol || !condition || !priceStr) return null;
|
|
87
87
|
return { symbol, condition, price: readAlertPrice(priceStr.replace(/^\$/, "")) };
|
|
88
88
|
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The edit dialog's text read against the alert it changes. An edit keeps the alert in its kind and its synced list:
|
|
92
|
+
* price stays price, a valuation or technical measure stays one, and a ratio or RRG alert keeps its listings.
|
|
93
|
+
*/
|
|
94
|
+
export function parseAlertEdit(
|
|
95
|
+
selected: { symbol: string; metric?: AlertMetric },
|
|
96
|
+
value: string,
|
|
97
|
+
): AlertCommandInput {
|
|
98
|
+
const parsed = parseAlertCommandValues({ shortcut: value });
|
|
99
|
+
if (!parsed) throw new Error("Use SYMBOL above|below|crosses PRICE.");
|
|
100
|
+
if (!!parsed.metric !== !!selected.metric) throw new Error("Add a new alert to change between price and a valuation or technical measure.");
|
|
101
|
+
if (!parsed.metric || !selected.metric) return parsed;
|
|
102
|
+
if (isRelativeMetric(parsed.metric) !== isRelativeMetric(selected.metric)) {
|
|
103
|
+
throw new Error("Add a new alert to change between a relative strength alert and a valuation or technical measure.");
|
|
104
|
+
}
|
|
105
|
+
if (!isRelativeMetric(selected.metric)) return parsed;
|
|
106
|
+
// The benchmark listing was confirmed when the alert was added; a different pair is a new alert.
|
|
107
|
+
if (parsed.metric.kind !== selected.metric.kind || parsed.symbol !== selected.symbol || !isRelativeMetric(parsed.metric)
|
|
108
|
+
|| parsed.metric.benchmark !== selected.metric.benchmark) {
|
|
109
|
+
throw new Error("Add a new alert to compare other listings.");
|
|
110
|
+
}
|
|
111
|
+
return { ...parsed, metric: { ...parsed.metric, benchmarkExchange: selected.metric.benchmarkExchange } };
|
|
112
|
+
}
|
|
@@ -8,13 +8,34 @@ export function ruleTarget(rule: EventAlertRule): string {
|
|
|
8
8
|
return `${target} · ${rule.eventDate ?? "date not known yet"}`;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Whether a rule is checked only for a signed-in account: Gloom Cloud checks every event rule but an IV level
|
|
13
|
+
* and an earnings, CPI, FOMC or calendar date, which the app reads itself, signed in or not, and the app reads
|
|
14
|
+
* catalysts only with a session. Signed out, such a rule never goes off.
|
|
15
|
+
*/
|
|
16
|
+
export function checkedWhenSignedIn(rule: EventAlertRule): boolean {
|
|
17
|
+
return rule.kind !== "iv_level" && !readScheduledRule(rule);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The state of a waiting rule nothing checks while signed out, as `alerts list` and its CSV write it. */
|
|
21
|
+
export const NEEDS_SIGN_IN = "needs sign-in";
|
|
22
|
+
|
|
23
|
+
/** What a list of rules says once when some of them need sign-in: what to do about it. */
|
|
24
|
+
export function needsSignInNote(count: number): string {
|
|
25
|
+
return `${count === 1 ? "1 event alert needs" : `${count} event alerts need`} sign-in: ${count === 1 ? "it is" : "they are"} checked only for a signed-in account. `
|
|
26
|
+
+ "Open the app (gloom) and log in or sign up free from the command bar (Ctrl+P, Log In or Sign Up).";
|
|
27
|
+
}
|
|
28
|
+
|
|
11
29
|
/**
|
|
12
30
|
* The state word the Events tab shows: a scheduled rule is Alerted once it has gone off for its coming
|
|
13
|
-
* event, an IV level once its level was reached, until it is re-armed.
|
|
31
|
+
* event, an IV level once its level was reached, until it is re-armed. Signed out, a waiting rule only a
|
|
32
|
+
* signed-in account gets checked needs sign-in.
|
|
14
33
|
*/
|
|
15
|
-
export function ruleState(rule: EventAlertRule, now: number = Date.now()): "Active" | "Paused" | "Alerted" {
|
|
34
|
+
export function ruleState(rule: EventAlertRule, now: number = Date.now(), signedIn = true): "Active" | "Paused" | "Alerted" | "Needs sign-in" {
|
|
16
35
|
const scheduled = readScheduledRule(rule);
|
|
17
36
|
const status = scheduled ? scheduledAlertStatus(rule, scheduled.leadDays, scheduledToday(scheduled.target, now), scheduled.trigger)
|
|
18
37
|
: rule.status === "active" && rule.kind === "iv_level" && rule.triggeredAt ? "triggered" : rule.status;
|
|
19
|
-
|
|
38
|
+
if (status === "paused") return "Paused";
|
|
39
|
+
if (status === "triggered") return "Alerted";
|
|
40
|
+
return !signedIn && checkedWhenSignedIn(rule) ? "Needs sign-in" : "Active";
|
|
20
41
|
}
|
|
@@ -31,19 +31,23 @@ import { alertKindLabel, fetchAlertHistory, ruleStateText } from "./history";
|
|
|
31
31
|
import { isResearchAlertKind } from "./research-rules";
|
|
32
32
|
import { useAsyncResource } from "../../../public/react";
|
|
33
33
|
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
34
|
+
import { apiClient } from "../../../api-client";
|
|
34
35
|
import { useResearchCloudSession } from "../shared/research-cloud-session";
|
|
35
36
|
import { rearmScheduledAlert } from "./scheduled";
|
|
36
37
|
import { checkScheduledEventAlertsNow } from "./scheduled-check";
|
|
37
38
|
import { checkIvLevelAlertsNow, IV_LEVEL_CHECKS_KEY, ivLevelCheckText, readIvLevelChecks, rearmIvLevelAlert } from "./iv-level";
|
|
38
39
|
import { alertTarget, alertViewHints, eventRuleSubject, useOpenAlertTarget } from "./targets";
|
|
40
|
+
import { FIRED_LOG_KEY } from "./fired-log";
|
|
41
|
+
import { withAlertChange } from "./alert-log";
|
|
39
42
|
|
|
40
|
-
const columnsFor = (width: number, lastCheck: boolean): DataTableColumn[] => [
|
|
43
|
+
const columnsFor = (width: number, lastCheck: boolean, needsSignIn: boolean): DataTableColumn[] => [
|
|
41
44
|
{ id: "kind", label: "Event", width: 15, align: "left" },
|
|
42
45
|
{ id: "target", label: "Following", width: 28, flexGrow: 1, align: "left" },
|
|
43
46
|
...(lastCheck && width >= 96
|
|
44
47
|
? [{ id: "last", label: "Last check", width: 34, align: "left" as const }]
|
|
45
48
|
: []),
|
|
46
|
-
|
|
49
|
+
// Wide enough to say "Needs sign-in" only when a row does.
|
|
50
|
+
{ id: "status", label: "State", width: needsSignIn ? 13 : 8, align: "left" },
|
|
47
51
|
{ id: "created", label: "Since", width: 10, align: "left" },
|
|
48
52
|
];
|
|
49
53
|
|
|
@@ -59,6 +63,8 @@ export function EventAlertsPane({ focused, width, height }: PaneProps) {
|
|
|
59
63
|
);
|
|
60
64
|
// Their readings stay on this device.
|
|
61
65
|
const [savedChecks] = usePluginState<unknown>(IV_LEVEL_CHECKS_KEY, {});
|
|
66
|
+
// Deleting or re-arming a rule goes in this device's alert log, beside the alerts that fired.
|
|
67
|
+
const [, setAlertLog] = usePluginState<unknown>(FIRED_LOG_KEY, []);
|
|
62
68
|
const checks = useMemo(() => readIvLevelChecks(savedChecks), [savedChecks]);
|
|
63
69
|
/** Changes one rule in the list it is kept in, leaving what that list holds of kinds this version does not know. */
|
|
64
70
|
const updateRules = useCallback(
|
|
@@ -93,10 +99,13 @@ export function EventAlertsPane({ focused, width, height }: PaneProps) {
|
|
|
93
99
|
const selectedRule = items[Math.min(selected, Math.max(0, items.length - 1))];
|
|
94
100
|
const now = Date.now();
|
|
95
101
|
const selectedFired = !!selectedRule && ruleState(selectedRule, now) === "Alerted";
|
|
96
|
-
const openTarget = useOpenAlertTarget();
|
|
97
|
-
const selectedTarget = useMemo(() => selectedRule ? alertTarget(eventRuleSubject(selectedRule)) : null, [selectedRule]);
|
|
98
102
|
// Research rules keep a dated observation in the Cloud; show its value and percentile.
|
|
99
103
|
const access = usePlanAccess();
|
|
104
|
+
// A saved session counts before its account has loaded, so a signed-in start does not flash "Needs sign-in".
|
|
105
|
+
const signedIn = access.signedIn || !!apiClient.getSessionToken();
|
|
106
|
+
const needsSignIn = !signedIn && rules.some((rule) => ruleState(rule, now, false) === "Needs sign-in");
|
|
107
|
+
const openTarget = useOpenAlertTarget();
|
|
108
|
+
const selectedTarget = useMemo(() => selectedRule ? alertTarget(eventRuleSubject(selectedRule)) : null, [selectedRule]);
|
|
100
109
|
const session = useResearchCloudSession();
|
|
101
110
|
const hasResearch = rules.some((rule) => isResearchAlertKind(rule.kind) && rule.kind !== "iv_level");
|
|
102
111
|
const stateLoader = useCallback(() => fetchAlertHistory(0), [session.requestKey]);
|
|
@@ -106,7 +115,7 @@ export function EventAlertsPane({ focused, width, height }: PaneProps) {
|
|
|
106
115
|
[history.data],
|
|
107
116
|
);
|
|
108
117
|
// An IV level is read by this app, so its last reading needs no session.
|
|
109
|
-
const columns = columnsFor(width, (hasResearch && !!history.data) || rules.some((rule) => rule.kind === "iv_level"));
|
|
118
|
+
const columns = columnsFor(width, (hasResearch && !!history.data) || rules.some((rule) => rule.kind === "iv_level"), needsSignIn);
|
|
110
119
|
const add = useCallback(
|
|
111
120
|
() => openPluginCommandWorkflow("set-event-alert"),
|
|
112
121
|
[openPluginCommandWorkflow],
|
|
@@ -123,11 +132,20 @@ export function EventAlertsPane({ focused, width, height }: PaneProps) {
|
|
|
123
132
|
(rule: EventAlertRule | undefined) => {
|
|
124
133
|
if (!rule) return;
|
|
125
134
|
const ivLevel = rule.kind === "iv_level";
|
|
126
|
-
|
|
135
|
+
const now = Date.now();
|
|
136
|
+
// Only a rule that went off is re-armed, as the list reads at the write: one still waiting is left, and not logged.
|
|
137
|
+
const rearmed: EventAlertRule[] = [];
|
|
138
|
+
updateRules(rule, (rules) => rules.map((entry) => {
|
|
139
|
+
if (entry.id !== rule.id || ruleState(entry, now) !== "Alerted") return entry;
|
|
140
|
+
rearmed.push(entry);
|
|
141
|
+
return ivLevel ? rearmIvLevelAlert(entry) : rearmScheduledAlert(entry);
|
|
142
|
+
}));
|
|
143
|
+
if (!rearmed.length) return;
|
|
144
|
+
for (const entry of rearmed) setAlertLog((saved: unknown) => withAlertChange(saved, "rearmed", entry));
|
|
127
145
|
// Once the write has landed, so it alerts now rather than at the next 15-minute check.
|
|
128
146
|
setTimeout(ivLevel ? checkIvLevelAlertsNow : checkScheduledEventAlertsNow, 1_000);
|
|
129
147
|
},
|
|
130
|
-
[updateRules],
|
|
148
|
+
[setAlertLog, updateRules],
|
|
131
149
|
);
|
|
132
150
|
const remove = useCallback(async () => {
|
|
133
151
|
if (!selectedRule) return;
|
|
@@ -137,8 +155,10 @@ export function EventAlertsPane({ focused, width, height }: PaneProps) {
|
|
|
137
155
|
body: [eventAlertTarget(rule)],
|
|
138
156
|
confirmLabel: "Delete",
|
|
139
157
|
});
|
|
140
|
-
if (confirmed)
|
|
141
|
-
|
|
158
|
+
if (!confirmed) return;
|
|
159
|
+
updateRules(rule, (rules) => rules.filter((entry) => entry.id !== rule.id));
|
|
160
|
+
setAlertLog((saved: unknown) => withAlertChange(saved, "deleted", rule));
|
|
161
|
+
}, [dialog, selectedRule, setAlertLog, updateRules]);
|
|
142
162
|
const status =
|
|
143
163
|
error ??
|
|
144
164
|
(selectedRule?.kind === "catalyst" ? catalystStatus : null) ??
|
|
@@ -201,7 +221,9 @@ export function EventAlertsPane({ focused, width, height }: PaneProps) {
|
|
|
201
221
|
// An IV level that could not be read says so on its own row: No data in State, the reason in Last check.
|
|
202
222
|
const check = rule.kind === "iv_level" ? checks[rule.id] : undefined;
|
|
203
223
|
const unread = rule.kind === "iv_level" && rule.status === "active" && !rule.triggeredAt && check?.value === null;
|
|
204
|
-
const state = ruleState(rule, now);
|
|
224
|
+
const state = ruleState(rule, now, signedIn);
|
|
225
|
+
// A rule nothing checks while signed out says so in its State, in the warning tone.
|
|
226
|
+
const waiting = state === "Needs sign-in" && column.id === "status";
|
|
205
227
|
return {
|
|
206
228
|
text:
|
|
207
229
|
column.id === "kind"
|
|
@@ -221,6 +243,8 @@ export function EventAlertsPane({ focused, width, height }: PaneProps) {
|
|
|
221
243
|
? colors.selectedText
|
|
222
244
|
: unread && (column.id === "status" || column.id === "last")
|
|
223
245
|
? colors.negative
|
|
246
|
+
: waiting
|
|
247
|
+
? colors.warning
|
|
224
248
|
: rule.status === "paused"
|
|
225
249
|
? colors.textMuted
|
|
226
250
|
: colors.text,
|
|
@@ -5,11 +5,19 @@ import { ALERTS_PLUGIN_ID } from "./constants";
|
|
|
5
5
|
/**
|
|
6
6
|
* The alerts this device's app fired, kept on the device (plugin resume
|
|
7
7
|
* state, never synced): `fn ALRT --tab history` and the History tab read it
|
|
8
|
-
* signed out, and merge Gloom Cloud's deliveries in when signed in.
|
|
8
|
+
* signed out, and merge Gloom Cloud's deliveries in when signed in. Beside
|
|
9
|
+
* them it keeps when a rule was created, re-armed or deleted here, from the
|
|
10
|
+
* app or `gloom alerts` (`action`).
|
|
9
11
|
*/
|
|
10
12
|
export const FIRED_LOG_KEY = "firedLog";
|
|
11
|
-
/** The newest
|
|
13
|
+
/** The newest fired alerts kept; older ones fall off as new ones are added. */
|
|
12
14
|
export const MAX_FIRED_ALERTS = 1000;
|
|
15
|
+
/**
|
|
16
|
+
* The newest rule changes (created, re-armed, deleted) kept, apart from the
|
|
17
|
+
* fired alerts: a change never counts against them, so no number of changes
|
|
18
|
+
* pushes an alert that went off out of the log.
|
|
19
|
+
*/
|
|
20
|
+
export const MAX_ALERT_CHANGES = 200;
|
|
13
21
|
|
|
14
22
|
export interface FiredAlert {
|
|
15
23
|
id: string;
|
|
@@ -32,10 +40,30 @@ export interface FiredAlert {
|
|
|
32
40
|
source: string | null;
|
|
33
41
|
symbol?: string;
|
|
34
42
|
exchange?: string;
|
|
43
|
+
/** A change to the rule rather than the alert going off: created, re-armed or deleted. */
|
|
44
|
+
action?: AlertLogAction;
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
export type FiredAlertInput = Omit<FiredAlert, "id" | "firedAt"> & { firedAt: number };
|
|
38
48
|
|
|
49
|
+
const ALERT_LOG_ACTIONS = ["created", "rearmed", "deleted"] as const;
|
|
50
|
+
export type AlertLogAction = (typeof ALERT_LOG_ACTIONS)[number];
|
|
51
|
+
/** What History says of a change, in its Value column. */
|
|
52
|
+
const ALERT_LOG_ACTION_TEXT: Record<AlertLogAction, string> = { created: "Created", rearmed: "Re-armed", deleted: "Deleted" };
|
|
53
|
+
|
|
54
|
+
/** A change to a rule as the log keeps it: when, what was done, and the rule as it read. */
|
|
55
|
+
export function alertChangeEntry(
|
|
56
|
+
action: AlertLogAction,
|
|
57
|
+
rule: { kind: string; ruleId: string; rule: string; symbol?: string; exchange?: string },
|
|
58
|
+
at = Date.now(),
|
|
59
|
+
): FiredAlertInput {
|
|
60
|
+
return {
|
|
61
|
+
kind: rule.kind, ruleId: rule.ruleId, rule: rule.rule, firedAt: at, value: null, quoteAt: null, source: null, action,
|
|
62
|
+
detail: ALERT_LOG_ACTION_TEXT[action],
|
|
63
|
+
...(rule.symbol ? { symbol: rule.symbol } : {}), ...(rule.exchange ? { exchange: rule.exchange } : {}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
39
67
|
/** How current a quote was, in the words reports use. */
|
|
40
68
|
export function quoteSourceWords(source: QuoteDataSource | undefined): string | null {
|
|
41
69
|
return source === "live" ? "real-time" : source === "delayed" ? "delayed" : source === "snapshot" ? "snapshot" : null;
|
|
@@ -57,7 +85,8 @@ export function readFiredAlerts(value: unknown): FiredAlert[] {
|
|
|
57
85
|
&& isText(entry.firedAt) && Number.isFinite(Date.parse(entry.firedAt))
|
|
58
86
|
&& (entry.value === null || Number.isFinite(entry.value))
|
|
59
87
|
&& (entry.quoteAt === null || isText(entry.quoteAt)) && (entry.source === null || isText(entry.source))
|
|
60
|
-
&& optionalText(entry.unit) && optionalText(entry.detail) && optionalText(entry.symbol) && optionalText(entry.exchange)
|
|
88
|
+
&& optionalText(entry.unit) && optionalText(entry.detail) && optionalText(entry.symbol) && optionalText(entry.exchange)
|
|
89
|
+
&& (entry.action === undefined || ALERT_LOG_ACTIONS.includes(entry.action)));
|
|
61
90
|
}
|
|
62
91
|
|
|
63
92
|
function firedAlertEntry(input: FiredAlertInput, id: string = crypto.randomUUID()): FiredAlert {
|
|
@@ -65,19 +94,39 @@ function firedAlertEntry(input: FiredAlertInput, id: string = crypto.randomUUID(
|
|
|
65
94
|
return { id, firedAt: firedAlertTime(firedAt) ?? new Date().toISOString(), ...rest };
|
|
66
95
|
}
|
|
67
96
|
|
|
68
|
-
/**
|
|
97
|
+
/**
|
|
98
|
+
* The log within its caps, oldest first: the newest MAX_FIRED_ALERTS fired
|
|
99
|
+
* alerts and, counted apart, the newest MAX_ALERT_CHANGES rule changes. Each
|
|
100
|
+
* kind of entry only ever drops its own oldest.
|
|
101
|
+
*/
|
|
102
|
+
function capAlertLog(entries: readonly FiredAlert[]): FiredAlert[] {
|
|
103
|
+
let changes = entries.reduce((count, entry) => count + (entry.action ? 1 : 0), 0);
|
|
104
|
+
let fired = entries.length - changes;
|
|
105
|
+
changes -= MAX_ALERT_CHANGES;
|
|
106
|
+
fired -= MAX_FIRED_ALERTS;
|
|
107
|
+
if (changes <= 0 && fired <= 0) return [...entries];
|
|
108
|
+
return entries.filter((entry) => {
|
|
109
|
+
if (entry.action ? changes <= 0 : fired <= 0) return true;
|
|
110
|
+
if (entry.action) changes -= 1;
|
|
111
|
+
else fired -= 1;
|
|
112
|
+
return false;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** `entries` with one more at the end, the oldest of its kind (fired alert or rule change) dropped past its cap. */
|
|
69
117
|
export function appendFiredAlert(entries: readonly FiredAlert[], input: FiredAlertInput, id: string = crypto.randomUUID()): FiredAlert[] {
|
|
70
|
-
return [...entries, firedAlertEntry(input, id)]
|
|
118
|
+
return capAlertLog([...entries, firedAlertEntry(input, id)]);
|
|
71
119
|
}
|
|
72
120
|
|
|
73
|
-
/** `entries` keeping only the newest `max` of each kind in `kinds`; other kinds are untouched. */
|
|
121
|
+
/** `entries` keeping only the newest `max` fired alerts of each kind in `kinds`; other kinds and rule changes are untouched. */
|
|
74
122
|
function capFiredAlertKinds(entries: readonly FiredAlert[], kinds: ReadonlySet<string>, max: number): FiredAlert[] {
|
|
123
|
+
const counted = (entry: FiredAlert) => !entry.action && kinds.has(entry.kind);
|
|
75
124
|
const over = new Map<string, number>();
|
|
76
|
-
for (const entry of entries) if (
|
|
125
|
+
for (const entry of entries) if (counted(entry)) over.set(entry.kind, (over.get(entry.kind) ?? 0) + 1);
|
|
77
126
|
for (const [kind, count] of over) over.set(kind, count - max);
|
|
78
127
|
// Oldest first: the first entries of a kind over its cap are the ones to drop.
|
|
79
128
|
return entries.filter((entry) => {
|
|
80
|
-
const extra = over.get(entry.kind) ?? 0;
|
|
129
|
+
const extra = counted(entry) ? over.get(entry.kind) ?? 0 : 0;
|
|
81
130
|
if (extra <= 0) return true;
|
|
82
131
|
over.set(entry.kind, extra - 1);
|
|
83
132
|
return false;
|
|
@@ -101,12 +150,27 @@ export function recordFiredAlertFor(
|
|
|
101
150
|
let entries = [...readFiredAlerts(runtime.getResumeState(ALERTS_PLUGIN_ID, FIRED_LOG_KEY)), ...inputs.map((input) => firedAlertEntry(input))];
|
|
102
151
|
// The source's own oldest entries go first, before the log's cap reaches anyone else's.
|
|
103
152
|
if (keepAtMost != null) entries = capFiredAlertKinds(entries, new Set(inputs.map((input) => input.kind)), keepAtMost);
|
|
104
|
-
runtime.setResumeState(ALERTS_PLUGIN_ID, FIRED_LOG_KEY, entries
|
|
153
|
+
runtime.setResumeState(ALERTS_PLUGIN_ID, FIRED_LOG_KEY, capAlertLog(entries));
|
|
105
154
|
} catch (error) {
|
|
106
155
|
onError(error);
|
|
107
156
|
}
|
|
108
157
|
}
|
|
109
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Adds entries to the device's log through the plugin's saved state as the
|
|
161
|
+
* shell reads and writes it (`gloom alerts`), where there is no running
|
|
162
|
+
* plugin to record them.
|
|
163
|
+
*/
|
|
164
|
+
export function appendAlertLog(
|
|
165
|
+
state: { get(key: string): unknown; set(key: string, value: unknown): void },
|
|
166
|
+
inputs: readonly FiredAlertInput[],
|
|
167
|
+
): void {
|
|
168
|
+
if (!inputs.length) return;
|
|
169
|
+
let entries = readFiredAlerts(state.get(FIRED_LOG_KEY));
|
|
170
|
+
for (const input of inputs) entries = appendFiredAlert(entries, input);
|
|
171
|
+
state.set(FIRED_LOG_KEY, entries);
|
|
172
|
+
}
|
|
173
|
+
|
|
110
174
|
/** Records a fired alert in the device's log. A log that cannot be written never stops the alert itself. */
|
|
111
175
|
export function createFiredAlertRecorder(resume: Pick<PluginResumeState, "getState" | "setState">, onError: (error: unknown) => void) {
|
|
112
176
|
return (input: FiredAlertInput) => {
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { closeBasis, metricReadingText, rotationQuadrant } from "./metric-model";
|
|
2
2
|
import { formatMarketPrice } from "../../../market-data/market/format";
|
|
3
|
+
import { formatShortDate } from "../../../utils/datetime-format";
|
|
3
4
|
import { formatQuoteAgeWithSource } from "../../../market-data/quotes/time";
|
|
4
5
|
import type { AlertCondition, AlertRule } from "./types";
|
|
5
6
|
|
|
7
|
+
/** What a list says of an alert no check has read yet. */
|
|
8
|
+
export const NOT_CHECKED_YET = "not checked yet";
|
|
9
|
+
|
|
6
10
|
export function relativeTime(ts: number): string {
|
|
7
11
|
const diff = Date.now() - ts;
|
|
8
12
|
const seconds = Math.floor(diff / 1000);
|
|
@@ -15,10 +19,16 @@ export function relativeTime(ts: number): string {
|
|
|
15
19
|
return `${days}d ago`;
|
|
16
20
|
}
|
|
17
21
|
|
|
22
|
+
/** A price ratio to four decimals, as GR states the latest ratio. */
|
|
23
|
+
const formatRatio = (value: number) => value.toFixed(Math.abs(value) >= 100 ? 2 : 4);
|
|
24
|
+
|
|
18
25
|
export function formatCurrentPrice(alert: AlertRule, maxWidth = 9): string {
|
|
19
26
|
if (alert.lastCheckError) return !alert.metric ? "No quote"
|
|
20
27
|
: /US listing|US equities|calendar/.test(alert.lastCheckError) ? "US only"
|
|
21
|
-
: /missing|bars|history/i.test(alert.lastCheckError) ? "Bar gap" : "No data";
|
|
28
|
+
: /missing|bars|history|weekly closes/i.test(alert.lastCheckError) ? "Bar gap" : "No data";
|
|
29
|
+
// The quadrant the latest weekly strength and momentum place it in.
|
|
30
|
+
if (alert.metric?.kind === "rrg") return rotationQuadrant(alert.lastCheckedPrice, alert.lastCheckedTarget) ?? "-";
|
|
31
|
+
if (alert.metric?.kind === "rs_ratio" && alert.lastCheckedPrice != null) return formatRatio(alert.lastCheckedPrice);
|
|
22
32
|
return alert.lastCheckedPrice == null
|
|
23
33
|
? "-"
|
|
24
34
|
: formatMarketPrice(alert.lastCheckedPrice, {
|
|
@@ -30,6 +40,8 @@ export function formatCurrentPrice(alert: AlertRule, maxWidth = 9): string {
|
|
|
30
40
|
export function formatAlertTargetPrice(alert: AlertRule, maxWidth = 9): string {
|
|
31
41
|
if (alert.metric?.kind === "ma_cross") return `${alert.metric.slowPeriod}-day MA`;
|
|
32
42
|
if (alert.metric?.kind === "close_ma") return `${alert.metric.period}-day MA`;
|
|
43
|
+
if (alert.metric?.kind === "rrg") return `${alert.metric.quadrant[0]!.toUpperCase()}${alert.metric.quadrant.slice(1)}`;
|
|
44
|
+
if (alert.metric?.kind === "rs_ratio") return formatRatio(alert.targetPrice);
|
|
33
45
|
return formatMarketPrice(alert.targetPrice, {
|
|
34
46
|
maxWidth,
|
|
35
47
|
minimumFractionDigits: 2,
|
|
@@ -38,7 +50,7 @@ export function formatAlertTargetPrice(alert: AlertRule, maxWidth = 9): string {
|
|
|
38
50
|
|
|
39
51
|
export function formatAlertDistance(alert: AlertRule): string {
|
|
40
52
|
const currentPrice = alert.lastCheckedPrice;
|
|
41
|
-
if (alert.lastCheckError) return "-";
|
|
53
|
+
if (alert.lastCheckError || alert.metric?.kind === "rrg") return "-";
|
|
42
54
|
if (currentPrice == null || !Number.isFinite(currentPrice) || currentPrice === 0) {
|
|
43
55
|
return "-";
|
|
44
56
|
}
|
|
@@ -52,7 +64,12 @@ export function formatAlertDistance(alert: AlertRule): string {
|
|
|
52
64
|
|
|
53
65
|
/** The Quote cell: the quote's age and feed, or, on the row of an alert that got none, why (a stale quote, or none). */
|
|
54
66
|
export function formatQuoteChecked(alert: AlertRule): string {
|
|
55
|
-
|
|
67
|
+
const basis = alert.metric ? closeBasis(alert.metric) : null;
|
|
68
|
+
// A settled close reads as the session it is from: Oct 26 close, Oct 23 weekly.
|
|
69
|
+
if (basis && alert.lastDailyClose && !alert.lastCheckError) {
|
|
70
|
+
return `${formatShortDate(alert.lastDailyClose, { year: false, utc: true })} ${basis === "weekly close" ? "weekly" : "close"}`;
|
|
71
|
+
}
|
|
72
|
+
if (basis) return `on ${basis}`;
|
|
56
73
|
if (alert.lastCheckError) return /\bstale\b/i.test(alert.lastCheckError) ? "Stale" : "No quote";
|
|
57
74
|
if (alert.lastQuoteUpdatedAt) {
|
|
58
75
|
return formatQuoteAgeWithSource({
|
|
@@ -61,7 +78,60 @@ export function formatQuoteChecked(alert: AlertRule): string {
|
|
|
61
78
|
});
|
|
62
79
|
}
|
|
63
80
|
if (alert.lastCheckedAt) return relativeTime(alert.lastCheckedAt);
|
|
64
|
-
return
|
|
81
|
+
return NOT_CHECKED_YET;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** A time as lists and exports give it: ISO 8601 in UTC, to the second (2026-10-27T06:35:12Z); empty for none. */
|
|
85
|
+
export function isoUtc(value: number | null | undefined): string {
|
|
86
|
+
return value != null && Number.isFinite(value) && value > 0 ? new Date(value).toISOString().replace(/\.\d{3}Z$/, "Z") : "";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The listing an alert is checked on: `EWG:ARCA`, its own exchange, or the
|
|
91
|
+
* venue its last quote came from for a rule set by symbol alone; the symbol
|
|
92
|
+
* until it has been checked.
|
|
93
|
+
*/
|
|
94
|
+
export function alertListing(alert: Pick<AlertRule, "symbol" | "exchange" | "lastQuoteExchange">): string {
|
|
95
|
+
const venue = alert.exchange || alert.lastQuoteExchange;
|
|
96
|
+
return venue && !alert.symbol.includes(":") ? `${alert.symbol}:${venue}` : alert.symbol;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** A listing with the currency its prices are in: `EWG:ARCA (USD)`; the listing alone until a quote has said it. */
|
|
100
|
+
export function listingWithCurrency(listing: string, currency: string | null | undefined): string {
|
|
101
|
+
return currency ? `${listing} (${currency})` : listing;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** The unit of an alert's reading: the listing's currency for a price or an average, `x` for a multiple, none for RSI. */
|
|
105
|
+
function readingUnit(alert: AlertRule): string {
|
|
106
|
+
const kind = alert.metric?.kind;
|
|
107
|
+
if (kind === "pe" || kind === "ps") return "x";
|
|
108
|
+
if (kind === "rsi" || kind === "rs_ratio" || kind === "rrg") return "";
|
|
109
|
+
return alert.lastQuoteCurrency ?? "";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** When the last reading is from: the quote's own time, or the daily close's; null with none. */
|
|
113
|
+
export function alertReadingAt(alert: AlertRule): number | null {
|
|
114
|
+
if (alert.lastCheckError || alert.lastCheckedPrice == null) return null;
|
|
115
|
+
return alert.lastQuoteUpdatedAt ?? alert.lastCheckedAt ?? null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The last reading an alert was checked on, with its unit and when it is
|
|
120
|
+
* from: `37.12 USD as of 2026-10-27T14:31Z`, `61.23 on the 2026-10-26 close`
|
|
121
|
+
* for a daily rule, why the last check read nothing, or `not checked yet`.
|
|
122
|
+
*/
|
|
123
|
+
export function alertReadingText(alert: AlertRule): string {
|
|
124
|
+
if (alert.lastCheckError) return `no reading: ${alert.lastCheckError.replace(/\.$/, "")}`;
|
|
125
|
+
const value = alert.lastCheckedPrice;
|
|
126
|
+
if (value == null || !Number.isFinite(value)) return NOT_CHECKED_YET;
|
|
127
|
+
const unit = readingUnit(alert);
|
|
128
|
+
const figure = alert.metric?.kind === "rrg" ? metricReadingText(alert.metric, { value, target: alert.lastCheckedTarget ?? Number.NaN })
|
|
129
|
+
: alert.metric?.kind === "rs_ratio" ? formatRatio(value)
|
|
130
|
+
: `${alert.metric ? value.toFixed(2) : String(value)}${unit === "x" ? "x" : unit ? ` ${unit}` : ""}`;
|
|
131
|
+
const basis = alert.metric ? closeBasis(alert.metric) : null;
|
|
132
|
+
if (basis && alert.lastDailyClose) return `${figure} on the ${alert.lastDailyClose} ${basis === "weekly close" ? "weekly close" : "close"}`;
|
|
133
|
+
const at = alertReadingAt(alert);
|
|
134
|
+
return at ? `${figure} as of ${isoUtc(at).replace(/:\d{2}Z$/, "Z")}` : figure;
|
|
65
135
|
}
|
|
66
136
|
|
|
67
137
|
export function conditionLabel(condition: AlertCondition): string {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { closeBasis, metricName, metricReadingText } from "./metric-model";
|
|
2
2
|
/**
|
|
3
3
|
* `gloom fn ALRT`: the Alerts pane's three tabs as reports. Prices lists
|
|
4
4
|
* the price alerts as the pane's table does, Events the event alerts (earnings,
|
|
@@ -10,9 +10,11 @@ import type { HeadlessPaneColumn, HeadlessPaneContext, HeadlessPaneDefinition, H
|
|
|
10
10
|
import type { PaneLock } from "../../../types/plugin";
|
|
11
11
|
import { refusedLock } from "../shared/cloud-denied";
|
|
12
12
|
import { ALERT_TABS, ALERTS_PLUGIN_ID } from "./constants";
|
|
13
|
-
import { ruleState } from "./event-rows";
|
|
13
|
+
import { needsSignInNote, ruleState } from "./event-rows";
|
|
14
14
|
import { eventAlertTarget, type EventAlertRule } from "./events";
|
|
15
15
|
import {
|
|
16
|
+
alertListing,
|
|
17
|
+
isoUtc,
|
|
16
18
|
conditionLabel,
|
|
17
19
|
formatAlertDistance,
|
|
18
20
|
formatAlertTargetPrice,
|
|
@@ -54,15 +56,26 @@ const PRICE_COLUMNS: HeadlessPaneColumn[] = [
|
|
|
54
56
|
{ key: "awayPercent", header: "Away", align: "right", format: percent, textFormat: (_value, row) => formatAlertDistance(asAlert(row)) },
|
|
55
57
|
{ key: "condition", header: "Trigger", format: (value, row) => {
|
|
56
58
|
const metric = asAlert(row).metric;
|
|
57
|
-
return metric?.kind === "ma_cross" ? `Cross ${metric.direction}` : conditionLabel(value as AlertRule["condition"]);
|
|
59
|
+
return metric?.kind === "ma_cross" ? `Cross ${metric.direction}` : metric?.kind === "rrg" ? "Enters" : conditionLabel(value as AlertRule["condition"]);
|
|
58
60
|
} },
|
|
59
61
|
{ key: "quoteAt", header: "Quote", textFormat: (_value, row) => formatQuoteChecked(asAlert(row)) },
|
|
60
62
|
{ key: "quoteSource", header: "Quote source" },
|
|
61
63
|
{ key: "lastCheckedAt", header: "Checked", format: instant },
|
|
62
64
|
{ key: "triggeredAt", header: "Alerted", format: instant, textFormat: (value) => (typeof value === "number" ? relativeTime(value) : "-") },
|
|
65
|
+
// After the columns exports have always had: when the rule was made, and the currency of its listing.
|
|
66
|
+
// To the second, as `alerts list` and the pane's detail write it: exports draw a stored millisecond time to the millisecond.
|
|
67
|
+
{ key: "created", header: "Created" },
|
|
68
|
+
{ key: "listing", header: "Listing" },
|
|
69
|
+
{ key: "currency", header: "Currency" },
|
|
70
|
+
// A measure's last reading in words, last so the columns before it keep their places: an RRG quadrant with the strength and momentum that place it.
|
|
71
|
+
{ key: "reading", header: "Reading" },
|
|
63
72
|
];
|
|
64
73
|
/** The terminal table keeps the pane's columns: the Quote cell already says the quote's age and feed. */
|
|
65
|
-
const PRICE_TEXT_COLUMNS =
|
|
74
|
+
const PRICE_TEXT_COLUMNS: HeadlessPaneColumn[] = [
|
|
75
|
+
...PRICE_COLUMNS.filter((column) => !["quoteSource", "lastCheckedAt", "created", "listing", "currency", "reading"].includes(column.key)),
|
|
76
|
+
// The day it was made, as the Events table lists its rules.
|
|
77
|
+
{ key: "createdAt", header: "Since", textFormat: (value) => (typeof value === "number" ? isoUtc(value).slice(0, 10) : "") },
|
|
78
|
+
];
|
|
66
79
|
|
|
67
80
|
const EVENT_COLUMNS: HeadlessPaneColumn[] = [
|
|
68
81
|
{ key: "event", header: "Event" },
|
|
@@ -98,13 +111,18 @@ const HISTORY_TEXT_COLUMNS = HISTORY_COLUMNS.filter((column) => column.key !== "
|
|
|
98
111
|
function priceRow(alert: AlertRule): HeadlessPaneRow {
|
|
99
112
|
const { lastQuoteProviderId: _provider, ...stored } = alert;
|
|
100
113
|
const price = alert.lastCheckError ? null : alert.lastCheckedPrice;
|
|
101
|
-
const away = price == null || !Number.isFinite(price) || price === 0 ? null : (((alert.lastCheckedTarget ?? alert.targetPrice) - price) / price) * 100;
|
|
114
|
+
const away = price == null || !Number.isFinite(price) || price === 0 || alert.metric?.kind === "rrg" ? null : (((alert.lastCheckedTarget ?? alert.targetPrice) - price) / price) * 100;
|
|
102
115
|
const quoted = !alert.lastCheckError;
|
|
116
|
+
const target = alert.lastCheckedTarget ?? alert.targetPrice;
|
|
103
117
|
return {
|
|
104
118
|
...stored,
|
|
105
119
|
awayPercent: away != null && Number.isFinite(away) ? Number(away.toFixed(2)) : null,
|
|
106
120
|
quoteAt: quoted ? firedAlertTime(alert.lastQuoteUpdatedAt) : null,
|
|
107
|
-
quoteSource: quoted ? alert.metric &&
|
|
121
|
+
quoteSource: quoted ? alert.metric && closeBasis(alert.metric) || quoteSourceWords(alert.lastQuoteSource) : null,
|
|
122
|
+
created: isoUtc(alert.createdAt) || null,
|
|
123
|
+
listing: alertListing(alert),
|
|
124
|
+
currency: alert.lastQuoteCurrency ?? null,
|
|
125
|
+
reading: alert.metric && price != null && Number.isFinite(price) && Number.isFinite(target) ? metricReadingText(alert.metric, { value: price, target }) : null,
|
|
108
126
|
};
|
|
109
127
|
}
|
|
110
128
|
|
|
@@ -123,7 +141,7 @@ function pricesResult(alerts: readonly AlertRule[], error: string | null, events
|
|
|
123
141
|
}
|
|
124
142
|
|
|
125
143
|
/** An event alert as the Events table lists it: a scheduled one with its event date and lead time apart. */
|
|
126
|
-
function eventRow(rule: EventAlertRule, now: number, checks: Record<string, IvLevelCheck
|
|
144
|
+
function eventRow(rule: EventAlertRule, now: number, checks: Record<string, IvLevelCheck>, signedIn: boolean): HeadlessPaneRow {
|
|
127
145
|
const scheduled = readScheduledRule(rule);
|
|
128
146
|
return {
|
|
129
147
|
id: rule.id,
|
|
@@ -133,22 +151,26 @@ function eventRow(rule: EventAlertRule, now: number, checks: Record<string, IvLe
|
|
|
133
151
|
eventDate: scheduled ? rule.eventDate ?? null : null,
|
|
134
152
|
leadDays: scheduled?.leadDays ?? null,
|
|
135
153
|
...(scheduled?.trigger === "results" ? { when: "results" } : {}),
|
|
136
|
-
state: ruleState(rule, now),
|
|
154
|
+
state: ruleState(rule, now, signedIn),
|
|
137
155
|
since: new Date(rule.createdAt).toISOString().slice(0, 10),
|
|
138
156
|
// An IV level's last reading, or why there was none, on its own row.
|
|
139
157
|
...rule.kind === "iv_level" ? { lastCheck: ivLevelCheckText(rule, checks[rule.id]) } : {},
|
|
140
158
|
};
|
|
141
159
|
}
|
|
142
160
|
|
|
143
|
-
function eventsResult(rules: readonly EventAlertRule[], error: string | null, checks: Record<string, IvLevelCheck
|
|
161
|
+
function eventsResult(rules: readonly EventAlertRule[], error: string | null, checks: Record<string, IvLevelCheck>, signedIn: boolean): HeadlessRowsResult {
|
|
144
162
|
const now = Date.now();
|
|
145
163
|
const checked = rules.some((rule) => rule.kind === "iv_level");
|
|
164
|
+
const rows = [...rules].sort((a, b) => b.createdAt - a.createdAt).map((rule) => eventRow(rule, now, checks, signedIn));
|
|
165
|
+
// Signed out, the rules only a signed-in account gets checked say so on their rows, and once what to do.
|
|
166
|
+
const waiting = rows.filter((row) => row.state === "Needs sign-in").length;
|
|
146
167
|
return {
|
|
147
168
|
// A Last check column only when an IV level rule has a reading to show.
|
|
148
169
|
columns: checked ? [...EVENT_COLUMNS.slice(0, -2), { key: "lastCheck", header: "Last check" }, ...EVENT_COLUMNS.slice(-2)] : EVENT_COLUMNS,
|
|
149
|
-
rows
|
|
170
|
+
rows,
|
|
150
171
|
empty: "No event alerts. Add one with gloom alerts add CRWV earnings --days 3, gloom alerts add CPI --days 1 or gloom alerts add CN PMI.",
|
|
151
172
|
...(error ? { errors: [error], complete: false } : {}),
|
|
173
|
+
...(waiting ? { notes: [needsSignInNote(waiting)] } : {}),
|
|
152
174
|
};
|
|
153
175
|
}
|
|
154
176
|
|
|
@@ -234,7 +256,7 @@ export const alertsHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
234
256
|
discovery: {
|
|
235
257
|
screenshotReadiness: "live-dom",
|
|
236
258
|
dataRequirements: ["Your saved alerts and this device's log of fired alerts; History adds Gloom Cloud's deliveries when signed in"],
|
|
237
|
-
limitations: ["Current price and quote time are what the app saved at
|
|
259
|
+
limitations: ["Current price and quote time are what the app or `gloom alerts add` saved at the last check; an alert neither has read says not checked yet."],
|
|
238
260
|
},
|
|
239
261
|
describe: (args) => `Alerts | ${String(args.options.tab ?? "prices")}`,
|
|
240
262
|
async load(args, ctx) {
|
|
@@ -242,7 +264,7 @@ export const alertsHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
242
264
|
if (tab === "history") return historyResult(ctx);
|
|
243
265
|
const { alerts, alertsError, events } = readAlertStores(ctx.config);
|
|
244
266
|
return tab === "events"
|
|
245
|
-
? eventsResult(events.rules, events.error, readIvLevelChecks(ctx.readDeviceState?.(ALERTS_PLUGIN_ID, IV_LEVEL_CHECKS_KEY)))
|
|
267
|
+
? eventsResult(events.rules, events.error, readIvLevelChecks(ctx.readDeviceState?.(ALERTS_PLUGIN_ID, IV_LEVEL_CHECKS_KEY)), !!ctx.apiClient.getSessionToken?.())
|
|
246
268
|
: pricesResult(alerts, alertsError, events.rules.length);
|
|
247
269
|
},
|
|
248
270
|
};
|
|
@@ -44,10 +44,11 @@ const upper = (value: unknown) => (typeof value === "string" && value ? value.to
|
|
|
44
44
|
* symbol, within the time the two take to send it.
|
|
45
45
|
*/
|
|
46
46
|
function sameAlert(entry: FiredAlert, item: AlertHistoryItem): boolean {
|
|
47
|
-
|
|
47
|
+
// A rule created, re-armed or deleted here is no delivery.
|
|
48
|
+
if (entry.action || item.kind !== entry.kind) return false;
|
|
48
49
|
const apart = Math.abs(Date.parse(item.deliveredAt) - Date.parse(entry.firedAt));
|
|
49
50
|
if (!Number.isFinite(apart)) return false;
|
|
50
|
-
if (entry.kind === "price" || ["pe", "ps", "rsi", "ma_cross", "close_ma"].includes(entry.kind)) return item.data?.alertId === entry.ruleId && apart <= PRICE_MATCH_MS;
|
|
51
|
+
if (entry.kind === "price" || ["pe", "ps", "rsi", "ma_cross", "close_ma", "rs_ratio", "rrg"].includes(entry.kind)) return item.data?.alertId === entry.ruleId && apart <= PRICE_MATCH_MS;
|
|
51
52
|
const symbol = upper(item.data?.symbol);
|
|
52
53
|
return !!symbol && symbol === upper(entry.symbol) && apart <= EVENT_MATCH_MS;
|
|
53
54
|
}
|
|
@@ -38,7 +38,7 @@ export interface AlertHistory {
|
|
|
38
38
|
const KIND_LABELS: Record<string, string> = {
|
|
39
39
|
catalyst: "Catalyst",
|
|
40
40
|
price: "Price",
|
|
41
|
-
pe: "P/E", ps: "P/S", rsi: "RSI", ma_cross: "MA crossover", close_ma: "Close vs MA",
|
|
41
|
+
pe: "P/E", ps: "P/S", rsi: "RSI", ma_cross: "MA crossover", close_ma: "Close vs MA", rs_ratio: "Ratio", rrg: "RRG quadrant",
|
|
42
42
|
breaking_news: "Breaking news",
|
|
43
43
|
earnings: "Earnings",
|
|
44
44
|
chat: "Chat",
|