@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
|
@@ -4,7 +4,7 @@ import type { DataProvider } from "../../types/data-provider";
|
|
|
4
4
|
import type { CliListing } from "../listing-arg";
|
|
5
5
|
import { cliStyles, cliTerminalWidth, wrapText } from "../../utils/cli-output";
|
|
6
6
|
import { isFiniteNumber } from "../../utils/guards";
|
|
7
|
-
import { calculateOptionGreeks, solveChainVolatilities } from "../../plugins/builtin/options/analytics";
|
|
7
|
+
import { calculateOptionGreeks, ivSolvedNote, solveChainVolatilities } from "../../plugins/builtin/options/analytics";
|
|
8
8
|
import { optionQuoteSide } from "../../plugins/builtin/options/market-reference";
|
|
9
9
|
import {
|
|
10
10
|
formatModelSpotPrice, modelSpotLabel, optionModelSpot, type OptionModelSpot,
|
|
@@ -16,9 +16,30 @@ import { DEFAULT_OPTION_CALC_DRAFT } from "../../plugins/builtin/options-calcula
|
|
|
16
16
|
import { expiryIsoDate, OPTION_EXPIRATION_PLACEHOLDER, parseOptionExpiration, settlementStampTime } from "../../utils/option-expiry";
|
|
17
17
|
import { loadModelDividendYield } from "../../market-data/model-dividend-yield";
|
|
18
18
|
import { formatUtcTime } from "../../utils/utc-time";
|
|
19
|
+
import { takeOption } from "./command-utils";
|
|
19
20
|
|
|
20
21
|
export const OPTIONS_USAGE = `options <symbol> [--expiration <${OPTION_EXPIRATION_PLACEHOLDER}>]`;
|
|
21
22
|
|
|
23
|
+
/** `--expiration` as `options` names it, and `--expiry` as `fn GEX` and `fn OPX` do. */
|
|
24
|
+
const OPTIONS_EXPIRATION_FLAGS = ["--expiration", "--expiry"] as const;
|
|
25
|
+
type OptionsExpirationFlag = (typeof OPTIONS_EXPIRATION_FLAGS)[number];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Takes the expiry option out of `args`, under either spelling: its value,
|
|
29
|
+
* the flag as typed (for messages), whether it was given at all (a flag with
|
|
30
|
+
* no value is given), and whether both spellings were.
|
|
31
|
+
*/
|
|
32
|
+
export function takeExpirationOption(args: string[]): {
|
|
33
|
+
value: string | undefined;
|
|
34
|
+
flag: OptionsExpirationFlag;
|
|
35
|
+
given: boolean;
|
|
36
|
+
both: boolean;
|
|
37
|
+
} {
|
|
38
|
+
const typed = OPTIONS_EXPIRATION_FLAGS.filter((flag) => args.some((arg) => arg === flag || arg.startsWith(`${flag}=`)));
|
|
39
|
+
const [expiration, expiry] = OPTIONS_EXPIRATION_FLAGS.map((flag) => takeOption(args, flag));
|
|
40
|
+
return { value: expiration ?? expiry, flag: typed.length === 1 ? typed[0]! : "--expiration", given: typed.length > 0, both: typed.length > 1 };
|
|
41
|
+
}
|
|
42
|
+
|
|
22
43
|
export { parseOptionExpiration };
|
|
23
44
|
|
|
24
45
|
/** Whether the chain holds contracts of the requested expiry, matched by UTC calendar date as the chain stamps them. */
|
|
@@ -63,6 +84,8 @@ export interface OptionModelInputs {
|
|
|
63
84
|
interface OptionModelFigures {
|
|
64
85
|
/** Annualized volatility as a fraction, solved from the chain's quotes. */
|
|
65
86
|
iv: number | null;
|
|
87
|
+
/** `iv` was read off the neighbouring strikes: this strike has no two-sided out-of-the-money quote to solve from. */
|
|
88
|
+
ivFilled?: boolean;
|
|
66
89
|
delta: number | null;
|
|
67
90
|
}
|
|
68
91
|
|
|
@@ -73,6 +96,7 @@ export const OPTION_MODEL_RATE = DEFAULT_OPTION_CALC_DRAFT.rate;
|
|
|
73
96
|
export const MARKED_OPTION_FIELDS = {
|
|
74
97
|
iv: "The venue's mark implied volatility (decimal), one per contract. Delta and the other greeks under mark are the venue's too: "
|
|
75
98
|
+ "Black-76 at the expiry's forward with no discounting, to its settlement.",
|
|
99
|
+
ivSource: "mark: iv is the venue's own, never solved or filled here.",
|
|
76
100
|
bid: "Prices in USD are the coin prices (coin) at the expiry's forward (mark.forward), the venue's own model price.",
|
|
77
101
|
mark: "price in USD; iv; delta and gamma per 1.00 move in the underlying; vega per volatility point and theta per calendar day, in USD.",
|
|
78
102
|
} as const;
|
|
@@ -91,6 +115,8 @@ export function chainCoinCurrency(chain: OptionsChain): string | null {
|
|
|
91
115
|
export const OPTION_VOLATILITY_FIELDS = {
|
|
92
116
|
iv: "Annualized volatility (decimal) solved from the quote midpoint, one per strike from its out-of-the-money side, at the "
|
|
93
117
|
+ "spot, valuation time and rate in metadata.model. Delta here and dealer gamma (GEX) are valued with it.",
|
|
118
|
+
ivSource: "solved: iv comes from the strike's out-of-the-money quote midpoint. filled: that side has no two-sided quote, so iv is read off the "
|
|
119
|
+
+ "nearest solved strikes (flat beyond the last one); it is no market's volatility, and delta is valued with it all the same.",
|
|
94
120
|
impliedVolatility: "The data provider's own reported implied volatility, passed through unchanged. Delta does not use it; "
|
|
95
121
|
+ "GEX falls back to it only at a strike with no two-sided quote on either side.",
|
|
96
122
|
} as const;
|
|
@@ -116,8 +142,9 @@ function optionModelFigures(chain: OptionsChain, inputs: OptionModelInputs): Map
|
|
|
116
142
|
for (const contract of contracts) {
|
|
117
143
|
const volatility = volatilities.byStrike.get(contract.strike);
|
|
118
144
|
const delta = calculateOptionGreeks(contract, side, inputs.spot, inputs.dividendYield, volatilities)?.delta;
|
|
145
|
+
const iv = volatility != null && Number.isFinite(volatility) && volatility > 0 ? volatility : null;
|
|
119
146
|
figures.set(contract, {
|
|
120
|
-
iv:
|
|
147
|
+
iv, ivFilled: iv != null && volatilities.filled.has(contract.strike),
|
|
121
148
|
delta: delta != null && Number.isFinite(delta) ? delta : null,
|
|
122
149
|
});
|
|
123
150
|
}
|
|
@@ -167,7 +194,8 @@ export async function loadOptionModelInputs(
|
|
|
167
194
|
/** The chain with each contract's modeled `iv` and `delta` added; every provider field is kept. */
|
|
168
195
|
export function chainWithModelFigures(chain: OptionsChain, inputs: OptionModelInputs): OptionsChain {
|
|
169
196
|
const figures = optionModelFigures(chain, inputs);
|
|
170
|
-
const add = (contract: OptionContract) => ({ ...contract, iv: figures.get(contract)?.iv ?? null,
|
|
197
|
+
const add = (contract: OptionContract) => ({ ...contract, iv: figures.get(contract)?.iv ?? null, ivFilled: figures.get(contract)?.ivFilled ?? false,
|
|
198
|
+
delta: figures.get(contract)?.delta ?? null });
|
|
171
199
|
return { ...chain, calls: chain.calls.map(add), puts: chain.puts.map(add) };
|
|
172
200
|
}
|
|
173
201
|
|
|
@@ -180,6 +208,13 @@ export interface OptionsChainBasis {
|
|
|
180
208
|
spot: OptionModelSpot | null;
|
|
181
209
|
dividendYield: number | null;
|
|
182
210
|
openInterest: OpenInterestAsOf | null;
|
|
211
|
+
/** A strike's IV is filled from its neighbours (`chainHasFilledIv`), which the closing line says. */
|
|
212
|
+
ivFilled?: boolean;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Whether any contract's IV was read off the neighbouring strikes (see `ChainVolatilities.filled`). */
|
|
216
|
+
export function chainHasFilledIv(chain: OptionsChain): boolean {
|
|
217
|
+
return [...chain.calls, ...chain.puts].some((contract) => (contract as OptionContract & OptionModelFigures).ivFilled === true);
|
|
183
218
|
}
|
|
184
219
|
|
|
185
220
|
/** The chain with `iv` and `delta` valued at the spot `optionModelSpot` picks for it, and that spot. */
|
|
@@ -207,7 +242,8 @@ export async function loadOptionsChainOpenInterest(symbol: string, chain: Option
|
|
|
207
242
|
}
|
|
208
243
|
|
|
209
244
|
/**
|
|
210
|
-
* The line under the chain: `Greeks at spot 89.79 (close Mon 12 Oct 20:00 UTC), dividend yield 2.34%, rate 4% ·
|
|
245
|
+
* The line under the chain: `Greeks at spot 89.79 (close Mon 12 Oct 20:00 UTC), dividend yield 2.34%, rate 4% · IV solved from quote mids ·
|
|
246
|
+
* OI as of Fri 9 Oct`, with `, ~ filled from neighbouring strikes` after the IV when a strike has no quote to solve from.
|
|
211
247
|
* An open interest whose date cannot be established says why. A chain its
|
|
212
248
|
* venue marks says whose marks they are: `Deribit marks: IV and greeks at
|
|
213
249
|
* each expiry's forward, rate 0%; $ is the BTC price at the forward · OI live, Thu 22 Oct 22:42 UTC`.
|
|
@@ -230,6 +266,7 @@ export function optionsBasisFootnote(basis: OptionsChainBasis, chain?: OptionsCh
|
|
|
230
266
|
// An unknown yield is valued as none, and says so rather than read as a stated 0%.
|
|
231
267
|
+ `dividend yield ${basis.dividendYield == null ? "unknown (valued at 0%)" : `${Number((basis.dividendYield * 100).toFixed(2))}%`}, `
|
|
232
268
|
+ `rate ${Number((OPTION_MODEL_RATE * 100).toFixed(2))}%`);
|
|
269
|
+
parts.push(ivSolvedNote(basis.ivFilled === true));
|
|
233
270
|
}
|
|
234
271
|
const oi = basis.openInterest;
|
|
235
272
|
parts.push(oi?.date ? openInterestAsOfLabel(oi)
|
|
@@ -266,6 +303,8 @@ export function optionRows(chain: OptionsChain) {
|
|
|
266
303
|
volume: contract.volume,
|
|
267
304
|
openInterest: contract.openInterest,
|
|
268
305
|
iv: modeled.iv ?? null,
|
|
306
|
+
// Solved from the strike's quote, or filled from its neighbours when it has none to solve from; a venue's own mark is neither.
|
|
307
|
+
ivSource: modeled.iv == null ? null : contract.mark ? "mark" : modeled.ivFilled ? "filled" : "solved",
|
|
269
308
|
delta: modeled.delta ?? null,
|
|
270
309
|
expiration: expiryIsoDate(contract.expiration),
|
|
271
310
|
// A venue's marks and coin prices (Deribit), beside the dollar ones.
|
|
@@ -301,9 +340,11 @@ export function formatOptionQuoteCell(row: Record<string, unknown>, side: "bid"
|
|
|
301
340
|
return quote == null ? "—" : String(quote);
|
|
302
341
|
}
|
|
303
342
|
|
|
304
|
-
/** Implied volatility as a percent; a dash when it could not be solved, never 0.0
|
|
305
|
-
export function formatOptionIvCell(value: unknown): string {
|
|
306
|
-
|
|
343
|
+
/** Implied volatility as a percent; a dash when it could not be solved, never 0.0%, and a leading "~" when it was filled from neighbouring strikes. */
|
|
344
|
+
export function formatOptionIvCell(value: unknown, row?: Record<string, unknown>): string {
|
|
345
|
+
if (!isFiniteNumber(value) || !(value > 0)) return "—";
|
|
346
|
+
const text = `${(value * 100).toFixed(1)}%`;
|
|
347
|
+
return row?.ivSource === "filled" ? `~${text}` : text;
|
|
307
348
|
}
|
|
308
349
|
|
|
309
350
|
/** Delta to two places, signed for puts; a dash when it could not be valued, and no "-0.00" for a far wing. */
|
|
@@ -281,6 +281,8 @@ export type LeapsRow = {
|
|
|
281
281
|
// The fields below are on a row only when `--columns` asked for them.
|
|
282
282
|
/** `iv`: the implied volatility the contract's delta is valued at, percent a year. */
|
|
283
283
|
ivPercent?: number | null;
|
|
284
|
+
/** `iv`: `filled` when that strike has no two-sided out-of-the-money quote to solve from and its IV is read off the neighbouring strikes. */
|
|
285
|
+
ivSource?: "solved" | "filled" | null;
|
|
284
286
|
/** `ivr`: the underlying's 30-day IV rank against its 52 weeks, 0 to 100, and the close it ranks. */
|
|
285
287
|
ivRank?: number | null;
|
|
286
288
|
ivRankAsOf?: string | null;
|
|
@@ -369,7 +371,10 @@ function screenLeapsChain(
|
|
|
369
371
|
dataSource: chain.dataSource ?? null,
|
|
370
372
|
delayMinutes: chain.delayMinutes ?? null,
|
|
371
373
|
};
|
|
372
|
-
if (wants.has("iv"))
|
|
374
|
+
if (wants.has("iv")) {
|
|
375
|
+
row.ivPercent = contract.iv != null && Number.isFinite(contract.iv) ? round(contract.iv * 100, 2) : null;
|
|
376
|
+
row.ivSource = row.ivPercent == null ? null : (contract as { ivFilled?: boolean }).ivFilled ? "filled" : "solved";
|
|
377
|
+
}
|
|
373
378
|
// A two-sided quote passed the spread cap, so the midpoint is there.
|
|
374
379
|
const mid = optionMid(contract)!;
|
|
375
380
|
if (wants.has("cost")) row.costPerContract = round(mid * SHARES_PER_CONTRACT, 2);
|
|
@@ -694,7 +699,7 @@ const LEAPS_COLUMNS: CliResultColumn<LeapsRow>[] = [
|
|
|
694
699
|
|
|
695
700
|
/** What each `--columns` name adds to the exports, after Extrinsic/yr %. */
|
|
696
701
|
const EXTRA_EXPORT_COLUMNS: Record<LeapsExtraColumn, CliResultColumn<LeapsRow>[]> = {
|
|
697
|
-
iv: [{ key: "ivPercent", header: "IV %", align: "right", format: formatPercent }],
|
|
702
|
+
iv: [{ key: "ivPercent", header: "IV %", align: "right", format: formatPercent }, { key: "ivSource", header: "IV source" }],
|
|
698
703
|
ivr: [{ key: "ivRank", header: "IV Rank", align: "right" }, { key: "ivRankAsOf", header: "IV Rank As Of" }],
|
|
699
704
|
cost: [{ key: "costPerContract", header: "Cost/Contract (mid x 100)", align: "right" }],
|
|
700
705
|
breakeven: [
|
|
@@ -717,7 +722,8 @@ const LEAPS_TEXT_COLUMNS: CliResultColumn<LeapsRow>[] = TEXT_COLUMN_KEYS.map((ke
|
|
|
717
722
|
|
|
718
723
|
/** The text table's columns for a contract's own `--columns` figures; the underlying's go in a table of their own above. */
|
|
719
724
|
const EXTRA_TEXT_COLUMNS: Partial<Record<LeapsExtraColumn, CliResultColumn<LeapsRow>[]>> = {
|
|
720
|
-
iv: [{ key: "ivPercent", header: "IV", align: "right", format:
|
|
725
|
+
iv: [{ key: "ivPercent", header: "IV", align: "right", format: (value, row) => row.ivSource === "filled" && typeof value === "number" && Number.isFinite(value)
|
|
726
|
+
? `~${value.toFixed(1)}%` : formatPercent(value) }],
|
|
721
727
|
"carry-net": [{ key: "carryNetPerYearPercent", header: "Net/yr", align: "right", format: formatPercent }],
|
|
722
728
|
cost: [{ key: "costPerContract", header: "Cost", align: "right", format: formatWhole }],
|
|
723
729
|
breakeven: [
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { logFailure } from "../../utils/log-failure";
|
|
2
2
|
import { apiClient } from "../../api-client";
|
|
3
|
-
import { ApiRequestError } from "../../api-client/errors";
|
|
3
|
+
import { ApiRequestError, isPermanentClientError } from "../../api-client/errors";
|
|
4
4
|
import type { CliCommandDef } from "../../types/plugin";
|
|
5
|
-
import { withCliServices, withMarketData } from "../context";
|
|
5
|
+
import { withCliServices, withConfigData, withMarketData } from "../context";
|
|
6
6
|
import { CLI_ERROR_CODE, failWithFields } from "../errors";
|
|
7
7
|
import type { CliResultColumn } from "../result";
|
|
8
8
|
import { formatCompact } from "../../utils/format";
|
|
@@ -23,25 +23,36 @@ import {
|
|
|
23
23
|
type MoverRankBasis,
|
|
24
24
|
} from "../../plugins/builtin/market-movers/model";
|
|
25
25
|
import { MOVER_CANDIDATES } from "../../plugins/builtin/market-movers/client";
|
|
26
|
+
import { hasPartialFloat, meetsMinFloat, minFloatShares, normalizeMinFloat, PARTIAL_FLOAT_NOTE } from "../../plugins/builtin/market-movers/min-float";
|
|
26
27
|
import { fetchEconCalendarFrom } from "../../plugins/builtin/econ/calendar-source";
|
|
27
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
attachFredSeriesPersistence, computedFredSeriesNote, loadCachedFredSeries, resetFredSeriesPersistence, seriesNotOnFred,
|
|
30
|
+
} from "../../sources/gloomberb-cloud/fred-series";
|
|
31
|
+
import type { CloudFredSeriesPayload } from "../../api-client";
|
|
32
|
+
import { savedCopyMark } from "../../data/saved-copies";
|
|
33
|
+
import { ratesMacroPluginMeta } from "../../plugins/builtin/builtin-plugin-meta";
|
|
34
|
+
import { createCommandPluginPersistence } from "../saved-copy-persistence";
|
|
28
35
|
import { EXTENDED_SESSION_LABELS, getQuoteSessionFields, type ExtendedSession } from "../../market-data/market/status";
|
|
29
36
|
import {
|
|
30
37
|
COUNTRY_CYCLE,
|
|
31
38
|
COUNTRY_FILTER_HINT,
|
|
39
|
+
awaitsConsensus,
|
|
32
40
|
countriesWithoutEvents,
|
|
41
|
+
econEventName,
|
|
33
42
|
FILTER_CYCLE,
|
|
34
43
|
loadCalendar,
|
|
35
44
|
eventCountry,
|
|
36
45
|
matchesCountry,
|
|
46
|
+
matchesEventSearch,
|
|
37
47
|
matchesImpact,
|
|
48
|
+
NO_CONSENSUS_NOTE,
|
|
38
49
|
noScheduledReleasesMessage,
|
|
39
50
|
parseCountryFilter,
|
|
40
51
|
shortCalendarEnd,
|
|
41
52
|
type CountryFilter,
|
|
42
53
|
type ImpactFilter,
|
|
43
54
|
} from "../../plugins/builtin/econ/calendar-model";
|
|
44
|
-
import { hasNoReleaseTime, hasNoStatedTime } from "../../plugins/builtin/econ/types";
|
|
55
|
+
import { hasNoReleaseTime, hasNoStatedTime, type EconEvent } from "../../plugins/builtin/econ/types";
|
|
45
56
|
import { econRowFigures } from "../../plugins/builtin/econ/figures";
|
|
46
57
|
import { isoDate, rejectExtraArgs, requireArg, requireOneArg, takeFlag, takeOption } from "./command-utils";
|
|
47
58
|
import { buildCorrelationSeries } from "../../plugins/builtin/correlation/matrix/model";
|
|
@@ -141,7 +152,7 @@ const MOVER_LISTS: Record<string, ScreenerCategory | "trending"> = {
|
|
|
141
152
|
"most-active": "most_actives",
|
|
142
153
|
trending: "trending",
|
|
143
154
|
};
|
|
144
|
-
const MOVERS_USAGE = "movers [gainers|losers|active|trending] [--session regular|extended]";
|
|
155
|
+
const MOVERS_USAGE = "movers [gainers|losers|active|trending] [--session regular|extended] [--min-float <shares>]";
|
|
145
156
|
/** The most rows the movers list serves in one request. */
|
|
146
157
|
const MOVER_REQUEST_CAP = 100;
|
|
147
158
|
/** A one-shot list waits this long for its rows' average volumes and sizes, as `fn MOST` does. */
|
|
@@ -154,6 +165,16 @@ function moverRankBasis(value: string | undefined, ctx: Parameters<CliCommandDef
|
|
|
154
165
|
return basis;
|
|
155
166
|
}
|
|
156
167
|
|
|
168
|
+
/** `--min-float` in shares, null for none; fails on a value that is not a share count. */
|
|
169
|
+
function moverMinFloat(value: string | undefined, ctx: Parameters<CliCommandDef["execute"]>[1]): number | null {
|
|
170
|
+
if (value == null) return null;
|
|
171
|
+
try {
|
|
172
|
+
return minFloatShares(normalizeMinFloat(value));
|
|
173
|
+
} catch (error) {
|
|
174
|
+
ctx.fail(error instanceof Error ? error.message : String(error));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
157
178
|
function screenerCategory(args: readonly string[], ctx: Parameters<CliCommandDef["execute"]>[1]): ScreenerCategory | "trending" {
|
|
158
179
|
rejectExtraArgs(args, 1, { usage: MOVERS_USAGE, takes: "one list" }, ctx);
|
|
159
180
|
const list = args[0]?.toLowerCase() ?? "gainers";
|
|
@@ -199,12 +220,14 @@ async function runMoverCommand(rawArgs: string[], ctx: Parameters<CliCommandDef[
|
|
|
199
220
|
const args = [...rawArgs];
|
|
200
221
|
const sessionOption = takeOption(args, "--session");
|
|
201
222
|
const basis = moverRankBasis(sessionOption, ctx);
|
|
223
|
+
const minFloat = moverMinFloat(takeOption(args, "--min-float"), ctx);
|
|
202
224
|
const category = screenerCategory(args, ctx);
|
|
203
225
|
const limit = ctx.cliOptions.limit ?? 25;
|
|
204
226
|
if (sessionOption != null && (category === "trending" || category === "most_actives")) {
|
|
205
227
|
ctx.fail("--session ranks Gainers and Losers.", category === "trending"
|
|
206
228
|
? "Trending keeps its source's order." : "Most active ranks by volume.");
|
|
207
229
|
}
|
|
230
|
+
if (minFloat != null && category === "trending") ctx.fail("--min-float filters Gainers, Losers and Most active.", "Trending carries no float.");
|
|
208
231
|
if (category === "trending") {
|
|
209
232
|
await withCliServices(ctx, async (services) => {
|
|
210
233
|
const trending = await fetchTrending(limit, undefined, { forceRefresh: ctx.cliOptions.refresh });
|
|
@@ -220,8 +243,8 @@ async function runMoverCommand(rawArgs: string[], ctx: Parameters<CliCommandDef[
|
|
|
220
243
|
}
|
|
221
244
|
|
|
222
245
|
// After the close a price list keeps only the rows moving its way on the basis
|
|
223
|
-
// asked for, so
|
|
224
|
-
const count = category === "most_actives" ? limit : Math.min(MOVER_REQUEST_CAP, Math.max(limit, MOVER_CANDIDATES));
|
|
246
|
+
// asked for, and a minimum float drops rows, so they read more candidates than they show.
|
|
247
|
+
const count = category === "most_actives" && minFloat == null ? limit : Math.min(MOVER_REQUEST_CAP, Math.max(limit, MOVER_CANDIDATES));
|
|
225
248
|
// With the saved session, as MOST reads it: a Pro account gets the real-time lists, anyone else the delayed ones.
|
|
226
249
|
const movers = await withMarketData(ctx, () =>
|
|
227
250
|
fetchPreferredMarketMovers(category, count, { forceRefresh: ctx.cliOptions.refresh, session: basis, metadataWaitMs: MOVER_METADATA_WAIT_MS }));
|
|
@@ -230,7 +253,7 @@ async function runMoverCommand(rawArgs: string[], ctx: Parameters<CliCommandDef[
|
|
|
230
253
|
// the regular session's move (its close's after the close), or with
|
|
231
254
|
// --session extended by the extended print's move from that close.
|
|
232
255
|
const { quotes, rankedBy } = rankMoverQuotes(category, movers.quotes.map(moverRegularSession), basis);
|
|
233
|
-
const rows = quotes.slice(0, limit).map((quote) => {
|
|
256
|
+
const rows = quotes.filter((quote) => meetsMinFloat(quote.floatShares, minFloat)).slice(0, limit).map((quote) => {
|
|
234
257
|
const { extended, ...row } = quote;
|
|
235
258
|
return {
|
|
236
259
|
...row,
|
|
@@ -244,16 +267,18 @@ async function runMoverCommand(rawArgs: string[], ctx: Parameters<CliCommandDef[
|
|
|
244
267
|
// As the MOST report reads it: real-time for a Pro account, delayed otherwise, each row dated by its own last price.
|
|
245
268
|
ctx.printResult({
|
|
246
269
|
data: rows,
|
|
247
|
-
metadata: { category, rankedBy },
|
|
270
|
+
metadata: { category, rankedBy, ...(minFloat != null ? { minFloat } : {}) },
|
|
248
271
|
...(fellBack ? { warnings: ["No pre-market or after-hours prints now, so the list is ranked by the regular-session move."] } : {}),
|
|
249
272
|
freshness: rowsFreshness(rows, { status: movers.source === "cloud" ? "live" : "delayed" }, { stale: movers.stale }),
|
|
250
273
|
}, {
|
|
251
274
|
heading: `Ranked by ${MOVER_RANKING_LABELS[rankedBy]}`,
|
|
275
|
+
...(minFloat != null && hasPartialFloat(rows) ? { footnote: PARTIAL_FLOAT_NOTE } : {}),
|
|
252
276
|
columns: [
|
|
253
277
|
...MOVER_COLUMNS.slice(0, 4),
|
|
254
278
|
...EXTENDED_COLUMNS,
|
|
255
279
|
{ key: "volume", header: "Volume", align: "right", value: (row) => formatCompact(Number(row.volume)) },
|
|
256
280
|
{ ...MARKET_CAP_CELL, value: (row) => row.marketCap == null ? "" : formatCompact(Number(row.marketCap)) },
|
|
281
|
+
...(minFloat != null ? [{ key: "floatShares", header: "Float", align: "right" as const, value: (row: Record<string, unknown>) => formatCompact(Number(row.floatShares)) }] : []),
|
|
257
282
|
],
|
|
258
283
|
});
|
|
259
284
|
}
|
|
@@ -288,7 +313,7 @@ function utcDateTimePart(value: unknown, part: "date" | "time", readerClock = tr
|
|
|
288
313
|
return part === "date" ? iso.slice(0, 10) : `${iso.slice(11, 16)} UTC${readerClock ? localTimeSuffix(date.getTime()) : ""}`;
|
|
289
314
|
}
|
|
290
315
|
|
|
291
|
-
const ECON_USAGE = "econ [--country <regions or countries>] [--impact <level>] [--from <date>] [--tz <zone>]";
|
|
316
|
+
const ECON_USAGE = "econ [--country <regions or countries>] [--impact <level>] [--search <words>] [--from <date>] [--tz <zone>]";
|
|
292
317
|
const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as const;
|
|
293
318
|
/** "Sat" for 2026-10-31. */
|
|
294
319
|
const weekdayOf = (date: string) => WEEKDAYS[new Date(`${date}T00:00:00Z`).getUTCDay()]!;
|
|
@@ -325,6 +350,19 @@ function econHeadColumns(viewerZone: string | null): CliResultColumn[] {
|
|
|
325
350
|
];
|
|
326
351
|
}
|
|
327
352
|
|
|
353
|
+
/**
|
|
354
|
+
* The text table's columns up to the event. Plain output keeps every column a
|
|
355
|
+
* query has, so a column that is empty by construction has to be left out
|
|
356
|
+
* here, not hidden by the table: the viewer's clock only exists with --tz, and
|
|
357
|
+
* the publisher's own clock only on rows a bank or statistics office times
|
|
358
|
+
* itself. Exports keep both, so a script's header never moves.
|
|
359
|
+
*/
|
|
360
|
+
function econTextHeadColumns(viewerZone: string | null, publisherTimed: boolean): CliResultColumn[] {
|
|
361
|
+
return econHeadColumns(viewerZone).filter((column) => (
|
|
362
|
+
column.key === "viewerTime" ? !!viewerZone : column.key === "localTime" ? publisherTimed : true
|
|
363
|
+
));
|
|
364
|
+
}
|
|
365
|
+
|
|
328
366
|
/**
|
|
329
367
|
* Actual, Forecast and Prior: as the text draws them (`30.3K`, `-4.1M bbl`) in the table, as
|
|
330
368
|
* numbers in the exports (30300, -4100000), where Unit and Scale say what they count and how they were drawn.
|
|
@@ -349,6 +387,9 @@ const econExportColumns: CliResultColumn[] = [
|
|
|
349
387
|
{ key: "actualText", header: "actual_text" },
|
|
350
388
|
{ key: "forecastText", header: "forecast_text" },
|
|
351
389
|
{ key: "priorText", header: "prior_text" },
|
|
390
|
+
// Which feed the forecast was read from and when, so a consensus can be told from another read of it.
|
|
391
|
+
{ key: "consensusFeed", header: "consensus_feed" },
|
|
392
|
+
{ key: "consensusReadAt", header: "consensus_read_at" },
|
|
352
393
|
];
|
|
353
394
|
|
|
354
395
|
/**
|
|
@@ -409,6 +450,16 @@ function blankTimeNotes(rows: ReadonlyArray<{ event: string; country: string; ti
|
|
|
409
450
|
return [...byNote].map(([note, events]) => `No time for ${events.join(", ")}: ${note.charAt(0).toLowerCase()}${note.slice(1)}.`);
|
|
410
451
|
}
|
|
411
452
|
|
|
453
|
+
/**
|
|
454
|
+
* Whether a row has come out: it has an actual, or it has no figure at all (a
|
|
455
|
+
* speech, a meeting, a holiday) and its time has passed. A row with a forecast
|
|
456
|
+
* or a prior and no actual is still to come, whatever its time.
|
|
457
|
+
*/
|
|
458
|
+
function cameOut(event: Pick<EconEvent, "actual" | "forecast" | "prior" | "date">, now: number): boolean {
|
|
459
|
+
if (event.actual?.trim()) return true;
|
|
460
|
+
return !event.forecast?.trim() && !event.prior?.trim() && event.date.getTime() <= now;
|
|
461
|
+
}
|
|
462
|
+
|
|
412
463
|
/**
|
|
413
464
|
* A `--from` the stored calendar does not reach back to, which Gloom Cloud refuses
|
|
414
465
|
* with "The calendar reaches back 400 days": an input error naming the earliest
|
|
@@ -429,15 +480,18 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
429
480
|
const rawArgs = [...args];
|
|
430
481
|
const country = parseCountry(takeOption(rawArgs, "--country"), ctx);
|
|
431
482
|
const impact: ImpactFilter = parseFilter(takeOption(rawArgs, "--impact"), FILTER_CYCLE, "impact", ctx);
|
|
483
|
+
const search = takeOption(rawArgs, "--search")?.trim() ?? null;
|
|
484
|
+
if (search === "") ctx.fail("--search needs the words of an event's name.", "Such as econ --search nonfarm.");
|
|
432
485
|
const fromOption = parseDateOption(takeOption(rawArgs, "--from"), "--from", ctx);
|
|
433
486
|
const tzOption = takeOption(rawArgs, "--tz");
|
|
434
487
|
const viewerZone = tzOption == null ? null : canonicalTimeZone(tzOption);
|
|
435
488
|
if (tzOption != null && !viewerZone) {
|
|
436
489
|
ctx.fail(`Unknown time zone "${tzOption}".`, "Use an IANA zone name, such as America/New_York, Europe/London or Asia/Tokyo.");
|
|
437
490
|
}
|
|
438
|
-
rejectArgs(rawArgs, ECON_USAGE, ctx, "Filter with --country and --
|
|
491
|
+
rejectArgs(rawArgs, ECON_USAGE, ctx, "Filter with --country, --impact and --search.");
|
|
439
492
|
await withCliServices(ctx, async () => {
|
|
440
|
-
const
|
|
493
|
+
const now = Date.now();
|
|
494
|
+
const todayStart = Math.floor(now / DAY_MS) * DAY_MS;
|
|
441
495
|
// An earlier day reads the stored calendar, which keeps past events after the week's listing drops them.
|
|
442
496
|
// A server without it, a server error or no network lists the current calendar (cached when offline) instead.
|
|
443
497
|
const stored = fromOption != null && fromOption < isoDate(new Date(todayStart))
|
|
@@ -445,7 +499,9 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
445
499
|
: null;
|
|
446
500
|
const past = stored?.events ? stored : null;
|
|
447
501
|
const unread = stored && !stored.events && stored.failure ? `The stored calendar could not be read (${stored.failure}); listing the current calendar` : null;
|
|
448
|
-
const { data: events, fetchedAt, stale } = past
|
|
502
|
+
const { data: events, fetchedAt, stale, source } = past
|
|
503
|
+
? { data: past.events, fetchedAt: Date.now(), stale: false, source: "network" as const }
|
|
504
|
+
: await loadCalendar(ctx.cliOptions.refresh);
|
|
449
505
|
// The calendar lists from today (UTC). --from moves the first day; --tail
|
|
450
506
|
// alone keeps the newest events of the whole calendar, the past week's too.
|
|
451
507
|
const from = fromOption ?? (ctx.cliOptions.tail != null ? null : new Date(todayStart).toISOString().slice(0, 10));
|
|
@@ -464,9 +520,10 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
464
520
|
// Countries asked for that nothing in the window is for: a quiet week there, not an unknown country.
|
|
465
521
|
const inWindow = events.filter((event) => event.date.getTime() >= fromTime);
|
|
466
522
|
const unscheduled = countriesWithoutEvents(country, inWindow);
|
|
467
|
-
const
|
|
468
|
-
.filter((event) => matchesCountry(event, country) && matchesImpact(event, impact))
|
|
469
|
-
.sort((left, right) => left.date.getTime() - right.date.getTime())
|
|
523
|
+
const listed = inWindow
|
|
524
|
+
.filter((event) => matchesCountry(event, country) && matchesImpact(event, impact) && (search == null || matchesEventSearch(event, search)))
|
|
525
|
+
.sort((left, right) => left.date.getTime() - right.date.getTime());
|
|
526
|
+
const rows = listed
|
|
470
527
|
.map((event) => {
|
|
471
528
|
// The viewer's clock (--tz) for a row with a time: its date and time there.
|
|
472
529
|
const clock = viewerZone && !hasNoReleaseTime(event) ? zonedClock(event.date.getTime(), viewerZone) : null;
|
|
@@ -480,7 +537,8 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
480
537
|
...(viewerZone ? { viewerDate: clock?.date ?? null, viewerTime: clock?.time ?? null } : {}),
|
|
481
538
|
country: eventCountry(event),
|
|
482
539
|
impact: event.impact,
|
|
483
|
-
|
|
540
|
+
// As the calendar names it: the Fed's decision is the upper bound of its range.
|
|
541
|
+
event: econEventName(event),
|
|
484
542
|
actual: figures.actual,
|
|
485
543
|
forecast: figures.forecast,
|
|
486
544
|
prior: figures.prior,
|
|
@@ -497,25 +555,34 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
497
555
|
// Who publishes the figure (Freddie Mac for the 30-year mortgage rate), the period it covers and its page.
|
|
498
556
|
publisher: event.source?.name ?? null,
|
|
499
557
|
referencePeriod: event.referencePeriod ?? null,
|
|
500
|
-
// When the figure came out: the row's time once it has an actual
|
|
501
|
-
|
|
558
|
+
// When the figure came out: the row's time once it has an actual, and for a row with no figures (a speech, a
|
|
559
|
+
// meeting) once its time has passed; its day alone when it states no time, null before.
|
|
560
|
+
released: cameOut(event, now) ? (hasNoReleaseTime(event) ? isoDate(event.date).slice(0, 10) : isoDate(event.date)) : null,
|
|
502
561
|
sourceUrl: event.source?.url ?? null,
|
|
503
|
-
// The same indicator from one release to the next
|
|
562
|
+
// The same indicator from one release to the next, as Gloom Cloud names it.
|
|
504
563
|
indicatorId: event.indicatorId ?? null,
|
|
564
|
+
// The feed the forecast was read from and when, for a row with a forecast.
|
|
565
|
+
consensusFeed: event.consensus?.source ?? null,
|
|
566
|
+
consensusReadAt: event.consensus?.readAt ?? null,
|
|
505
567
|
};
|
|
506
568
|
});
|
|
507
569
|
// A filter of countries only, none of which has anything in the window, says so as its empty list.
|
|
508
570
|
const allUnscheduled = unscheduled.length > 0 && unscheduled.length === country.split(",").length;
|
|
509
571
|
const notes = [listedBefore, listedAfter, ...(rows.length > 0 ? blankTimeNotes(rows) : []),
|
|
510
|
-
rows.length > 0 && unscheduled.length > 0 ? noScheduledReleasesMessage(unscheduled) : null
|
|
572
|
+
rows.length > 0 && unscheduled.length > 0 ? noScheduledReleasesMessage(unscheduled) : null,
|
|
573
|
+
// A dash under Forecast on a release still to come is a consensus not in yet, not a missing figure.
|
|
574
|
+
listed.some(awaitsConsensus) ? NO_CONSENSUS_NOTE : null]
|
|
511
575
|
.filter((note): note is string => !!note);
|
|
512
576
|
const footnote = notes.join("\n");
|
|
577
|
+
// The calendar as read, not its rows: those are scheduled, mostly ahead. One that could not be
|
|
578
|
+
// refreshed is the copy an earlier read saved, and says when that was.
|
|
579
|
+
const read = rowsFreshness([], { status: "not-a-feed", basis: "calendar", asOf: fetchedAt }, { stale });
|
|
580
|
+
const calendarFreshness = read && source === "stale-fallback" ? { ...read, savedAt: new Date(fetchedAt).toISOString() } : read;
|
|
513
581
|
ctx.printResult({
|
|
514
582
|
data: rows,
|
|
515
583
|
...(unread ? { warnings: [unread] } : {}),
|
|
516
|
-
metadata: { country, impact, from, listedThrough, storedFrom: past?.storedFrom ?? null, ...(viewerZone ? { timeZone: viewerZone } : {}) },
|
|
517
|
-
|
|
518
|
-
freshness: rowsFreshness([], { status: "not-a-feed", basis: "calendar", asOf: fetchedAt }, { stale }),
|
|
584
|
+
metadata: { country, impact, ...(search != null ? { search } : {}), from, listedThrough, storedFrom: past?.storedFrom ?? null, ...(viewerZone ? { timeZone: viewerZone } : {}) },
|
|
585
|
+
freshness: calendarFreshness,
|
|
519
586
|
}, {
|
|
520
587
|
dateKey: "date",
|
|
521
588
|
defaultLimit: 50,
|
|
@@ -535,7 +602,7 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
535
602
|
}),
|
|
536
603
|
// The exports add what a script needs to read a row; the text table keeps the columns it had.
|
|
537
604
|
columns: [...econHeadColumns(viewerZone), ...econFigureColumns(false), ...econExportColumns],
|
|
538
|
-
textColumns: [...
|
|
605
|
+
textColumns: [...econTextHeadColumns(viewerZone, rows.some((row) => !!publisherClock(row.localTime, row.timeZone))), ...econFigureColumns(true), ECON_PUBLISHER_COLUMN],
|
|
539
606
|
});
|
|
540
607
|
});
|
|
541
608
|
}
|
|
@@ -561,6 +628,36 @@ function isUnsupportedSeries(error: unknown): boolean {
|
|
|
561
628
|
return error.details?.code === "unsupported_series" || /^Unsupported FRED series\b/i.test(message);
|
|
562
629
|
}
|
|
563
630
|
|
|
631
|
+
/**
|
|
632
|
+
* Runs `run` with the FRED series cache saving what it fetches in the profile,
|
|
633
|
+
* where the app's rates and macro panes keep it: a refresh that fails then
|
|
634
|
+
* answers from the copy an earlier read saved, and the report says so.
|
|
635
|
+
*/
|
|
636
|
+
function withSavedFredSeries<T>(ctx: Parameters<CliCommandDef["execute"]>[1], run: () => Promise<T>): Promise<T> {
|
|
637
|
+
return withConfigData(ctx, async ({ persistence }) => {
|
|
638
|
+
attachFredSeriesPersistence(createCommandPluginPersistence(persistence.resources, `plugin:${ratesMacroPluginMeta.stateId}`));
|
|
639
|
+
try {
|
|
640
|
+
return await run();
|
|
641
|
+
} finally {
|
|
642
|
+
resetFredSeriesPersistence();
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* A FRED series from Gloom Cloud, or the copy an earlier read saved when it
|
|
649
|
+
* cannot be refreshed (marked as such). A refusal asking again will not change
|
|
650
|
+
* (a series Gloom Cloud no longer serves, access denied) is said, not covered.
|
|
651
|
+
*/
|
|
652
|
+
async function loadFredSeries(
|
|
653
|
+
seriesId: string,
|
|
654
|
+
params: { startDate?: string; limit?: number; sortOrder: "asc" | "desc" },
|
|
655
|
+
): Promise<CloudFredSeriesPayload> {
|
|
656
|
+
const result = await loadCachedFredSeries({ seriesId, ...params }, () => apiClient.getCloudFredSeries(seriesId, params));
|
|
657
|
+
if (result.source === "stale-fallback" && isPermanentClientError(result.error)) throw result.error;
|
|
658
|
+
return result.data;
|
|
659
|
+
}
|
|
660
|
+
|
|
564
661
|
async function runFredList(filter: string, ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
565
662
|
let catalog;
|
|
566
663
|
try {
|
|
@@ -606,9 +703,19 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
606
703
|
ctx.fail(change ? `Unknown change "${change}".` : "--change needs 1D or 1W.", "Use 1D (from the previous observation) or 1W (from a week before).");
|
|
607
704
|
}
|
|
608
705
|
const seriesId = requireOneArg(args, FRED_USAGE, "series", ctx).toUpperCase();
|
|
706
|
+
await withSavedFredSeries(ctx, () => printFredSeries(ctx, { seriesId, start, startDate, sortOrder, horizon }));
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
async function printFredSeries(ctx: Parameters<CliCommandDef["execute"]>[1], { seriesId, start, startDate, sortOrder, horizon }: {
|
|
710
|
+
seriesId: string;
|
|
711
|
+
start: string | undefined;
|
|
712
|
+
startDate: string;
|
|
713
|
+
sortOrder: "asc" | "desc";
|
|
714
|
+
horizon: ReturnType<typeof parseFredChange>;
|
|
715
|
+
}) {
|
|
609
716
|
// A change needs the observation it is measured from: the fetch reaches back before the first row shown.
|
|
610
717
|
const fetchStart = horizon ? fredChangeFetchStart(startDate) : startDate;
|
|
611
|
-
const data = await
|
|
718
|
+
const data = await loadFredSeries(seriesId, { startDate: fetchStart, sortOrder }).catch((error: unknown) => {
|
|
612
719
|
if (isUnsupportedSeries(error)) ctx.fail(`Unsupported FRED series ${seriesId}. List supported series with: gloom fred --list`);
|
|
613
720
|
throw error;
|
|
614
721
|
});
|
|
@@ -622,7 +729,9 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
622
729
|
const releasedOf = (observation: { publisher?: string; publishedAt?: string; releasedOn?: string }) =>
|
|
623
730
|
observation.releasedOn ?? (observation.publisher?.trim() ? observation.publishedAt : undefined);
|
|
624
731
|
// A value FRED does not list yet carries its publisher and when it published, in every export.
|
|
732
|
+
// A row from the copy an earlier read saved, the refresh having failed, carries its mark: the source line says so.
|
|
625
733
|
const exportRow = (observation: { date: string; value: number | null; publisher?: string; publishedAt?: string; releasedOn?: string }) => ({
|
|
734
|
+
...savedCopyMark(data),
|
|
626
735
|
date: observation.date,
|
|
627
736
|
value: fredExportValue(seriesId, observation.value),
|
|
628
737
|
...(changes ? { change: changes.get(observation.date)?.change ?? null, changeFrom: changes.get(observation.date)?.changeFrom ?? null } : {}),
|
|
@@ -641,7 +750,7 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
641
750
|
computedFredSeriesNote(seriesId),
|
|
642
751
|
].filter((note): note is string => note != null);
|
|
643
752
|
// Nothing on or after the start: name where the series ends, so an empty answer is not a dead end.
|
|
644
|
-
const latest = rows.length > 0 ? null : await
|
|
753
|
+
const latest = rows.length > 0 ? null : await loadFredSeries(seriesId, { limit: 1, sortOrder: "desc" })
|
|
645
754
|
.then((payload) => payload.observations[0] ?? null).catch(logFailure("cli.commands-overview", "Read latest economic series observation failed", null));
|
|
646
755
|
// CSV and NDJSON are rows alone: an empty one would read as a series with no data, so it fails and says where the series ends.
|
|
647
756
|
if (rows.length === 0 && (ctx.cliOptions.format === "csv" || ctx.cliOptions.format === "ndjson")) {
|
|
@@ -713,10 +822,12 @@ async function runYieldCurve(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
713
822
|
const startDate = parseStartDate(takeOption(args, "--start"), ctx);
|
|
714
823
|
rejectArgs(args, "yield-curve [--start <date>]", ctx);
|
|
715
824
|
const series = Object.keys(YIELD_TENORS);
|
|
716
|
-
const results = await Promise.all(series.map(async (seriesId) => {
|
|
717
|
-
const data = await
|
|
825
|
+
const results = await withSavedFredSeries(ctx, () => Promise.all(series.map(async (seriesId) => {
|
|
826
|
+
const data = await loadFredSeries(seriesId, { startDate, sortOrder: "desc" });
|
|
718
827
|
const [latest, previous] = data.observations;
|
|
719
828
|
return {
|
|
829
|
+
// A tenor from the copy an earlier read saved, the refresh having failed, says so in the source line.
|
|
830
|
+
...savedCopyMark(data),
|
|
720
831
|
seriesId,
|
|
721
832
|
date: latest?.date ?? "",
|
|
722
833
|
// Yields in percent, which the exports' `currency_or_unit` column says (`pct`), as `fred` does.
|
|
@@ -729,7 +840,7 @@ async function runYieldCurve(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
729
840
|
latest,
|
|
730
841
|
publishedAt: latest?.publisher ? null : fredPublicationTime(data.info, latest?.date),
|
|
731
842
|
};
|
|
732
|
-
}));
|
|
843
|
+
})));
|
|
733
844
|
// Tenors whose latest yield FRED does not list yet name the Treasury and when it published them.
|
|
734
845
|
const ahead = fredProvenance(results.flatMap((row) => row.latest ? [row.latest] : []));
|
|
735
846
|
// The curve shown was published with its last tenor: the newest of their FRED updates, when each has one.
|
|
@@ -826,8 +937,11 @@ export const overviewCliCommands: CliCommandDef[] = [
|
|
|
826
937
|
options: [{
|
|
827
938
|
flags: "--session <basis>",
|
|
828
939
|
description: "Rank gainers and losers by the regular-session move (regular, the default) or by the pre-market or after-hours move from the close (extended)",
|
|
940
|
+
}, {
|
|
941
|
+
flags: "--min-float <shares>",
|
|
942
|
+
description: "Keep the gainers, losers or most active whose float (the one EQS serves) is at least this many shares, such as 10M or 500k; a row with no float is left out",
|
|
829
943
|
}],
|
|
830
|
-
examples: ["movers", "movers losers --limit 10", "movers gainers --session extended", "movers trending"],
|
|
944
|
+
examples: ["movers", "movers losers --limit 10", "movers gainers --session extended", "movers gainers --min-float 10M", "movers trending"],
|
|
831
945
|
},
|
|
832
946
|
execute: runMoverCommand,
|
|
833
947
|
},
|
|
@@ -858,10 +972,11 @@ export const overviewCliCommands: CliCommandDef[] = [
|
|
|
858
972
|
options: [
|
|
859
973
|
{ flags: "--country <regions or countries>", description: "US, G7, EU, all (default), or countries by their two-letter codes, comma separated, such as MX or MX,BR,ZA" },
|
|
860
974
|
{ flags: "--impact <level>", description: "high, medium, low, or all (default all)" },
|
|
975
|
+
{ flags: "--search <words>", description: "Events whose name has these words, case, spaces and hyphens aside, such as nonfarm or fed funds" },
|
|
861
976
|
{ flags: "--from <date>", description: `First day listed, as ${DATE_OPTION_EXAMPLES} (default today, UTC); an earlier day lists past events from the stored calendar and says where it starts` },
|
|
862
977
|
{ flags: "--tz <zone>", description: "Add a column with each release's time on that clock, an IANA zone such as America/New_York; with the weekday when it falls on another day than the UTC date" },
|
|
863
978
|
],
|
|
864
|
-
examples: ["econ", "econ --country US --impact high", "econ --country MX,BR,ZA,IN,KR", "econ --from 2026-10-12 --country US",
|
|
979
|
+
examples: ["econ", "econ --country US --impact high", "econ --search nonfarm", "econ --country MX,BR,ZA,IN,KR", "econ --from 2026-10-12 --country US",
|
|
865
980
|
"econ --country BR --tz America/New_York", "econ --tail 20"],
|
|
866
981
|
},
|
|
867
982
|
execute: runEcon,
|
|
@@ -133,7 +133,7 @@ export async function searchCandidatesForCli({
|
|
|
133
133
|
const quote = BARE_SYMBOL.test(symbol) && candidates.some((candidate) => candidate.symbol.toUpperCase() === symbol)
|
|
134
134
|
? await (quoted ?? loadSearchedQuote(symbol, savedExchange, dataProvider))
|
|
135
135
|
: null;
|
|
136
|
-
return withQuotedNames(symbol, withHomeListingPrimary(trimmedQuery, withPrimaryListingFirst(symbol, candidates, quote)), quote)
|
|
136
|
+
return withQuotedNames(symbol, withUsLineOfPrimary(withHomeListingPrimary(trimmedQuery, withPrimaryListingFirst(symbol, candidates, quote))), quote)
|
|
137
137
|
.slice(0, totalLimit);
|
|
138
138
|
} catch (error) {
|
|
139
139
|
// Not "No matches found": a script or a reader would take that for a name nothing lists.
|
|
@@ -299,6 +299,26 @@ function withHomeListingPrimary(query: string, candidates: TickerSearchCandidate
|
|
|
299
299
|
return [...rest.slice(0, at), { ...home, category: "Primary Listing" as const }, { ...primary, category: "Other Listings" as const }, ...rest.slice(at + 1)];
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
+
/**
|
|
303
|
+
* A company whose primary listing is abroad lists the US line of the same
|
|
304
|
+
* symbol right after it: the line `quote SHEL` reads (a bare symbol is its US
|
|
305
|
+
* listing), which a name search ("shell") would otherwise rank below the
|
|
306
|
+
* company's lines on other venues and cut. Any other list keeps its order.
|
|
307
|
+
*/
|
|
308
|
+
function withUsLineOfPrimary(candidates: TickerSearchCandidate[]): TickerSearchCandidate[] {
|
|
309
|
+
const primary = candidates.find((candidate) => candidate.category === "Primary Listing");
|
|
310
|
+
if (!primary || primary.kind !== "search" || isUsListingExchange(candidateExchange(primary))) return candidates;
|
|
311
|
+
const symbol = primary.symbol.toUpperCase();
|
|
312
|
+
const company = resolveSearchName(primary);
|
|
313
|
+
const us = candidates.find((candidate) => candidate !== primary && candidate.kind === "search" && !candidate.contractKey
|
|
314
|
+
&& candidate.category !== "Funds & Derivatives" && candidate.symbol.toUpperCase() === symbol
|
|
315
|
+
&& isUsListingExchange(candidateExchange(candidate)) && sameCompany(resolveSearchName(candidate), company));
|
|
316
|
+
if (!us) return candidates;
|
|
317
|
+
const rest = candidates.filter((candidate) => candidate !== us);
|
|
318
|
+
const at = rest.indexOf(primary) + 1;
|
|
319
|
+
return [...rest.slice(0, at), { ...us, category: "Other Listings" as const }, ...rest.slice(at)];
|
|
320
|
+
}
|
|
321
|
+
|
|
302
322
|
/** The candidate on the venue the quote prices; the US exchanges share one symbol, so a quote on any of them is its one US line. */
|
|
303
323
|
function quotedCandidate(candidates: readonly TickerSearchCandidate[], quote: Quote | null): TickerSearchCandidate | undefined {
|
|
304
324
|
const quoted = canonicalExchange(quote?.listingExchangeName || quote?.exchangeName);
|