gloomberb 0.17.4 → 0.17.6
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/package.json +3 -1
- package/patches/electrobun@1.18.1.patch +80 -0
- package/src/api-client/chat.ts +9 -0
- package/src/api-client/commodity-balances.ts +77 -0
- package/src/api-client/data.ts +35 -3
- package/src/api-client/earnings.ts +10 -0
- package/src/api-client/index.ts +5 -0
- package/src/api-client/insider-signals.ts +75 -0
- package/src/api-client/market-discovery.ts +1 -1
- package/src/api-client/normalizers.ts +1 -0
- package/src/api-client/paths.ts +9 -0
- package/src/api-client/research-activity.ts +10 -3
- package/src/api-client/socket.ts +10 -0
- package/src/api-client/types.ts +25 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +2 -1
- package/src/app/runtime/broker-import.ts +23 -21
- package/src/app/runtime/plugin-bindings.ts +13 -7
- package/src/app/runtime/update.ts +64 -4
- package/src/app.tsx +2 -0
- package/src/architecture/core-builtin-allowlist.json +2 -10
- package/src/ask-gloom/pane-context.ts +11 -1
- package/src/brokers/broker-portfolio-sync.ts +66 -0
- package/src/brokers/sync-broker-instance.ts +75 -41
- package/src/cli/commands/market.ts +101 -34
- package/src/cli/commands/options-chain.ts +20 -6
- package/src/cli/commands/overview.ts +54 -13
- package/src/cli/commands/search.ts +1 -1
- package/src/cli/commands/system.ts +19 -1
- package/src/cli/commands/ticker.ts +24 -4
- package/src/cli/context.ts +13 -0
- package/src/cli/errors.ts +15 -2
- package/src/cli/fx-pair.ts +77 -0
- package/src/cli/glossary.ts +34 -4
- package/src/cli/history-rows.ts +25 -11
- package/src/cli/index.ts +30 -12
- package/src/cli/listing-arg.ts +61 -138
- package/src/cli/listing-suggestion.ts +3 -21
- package/src/cli/pane-functions/catalog.ts +50 -0
- package/src/cli/pane-functions/freshness.ts +44 -9
- package/src/cli/pane-functions/function-help.ts +9 -9
- package/src/cli/pane-functions/headless.ts +19 -7
- package/src/cli/pane-functions/index.ts +63 -5
- package/src/cli/pane-functions/market-session.ts +18 -3
- package/src/cli/pane-functions/resolver.ts +29 -8
- package/src/cli/pane-functions/screenshot.ts +2 -0
- package/src/cli/report-tables.ts +11 -4
- package/src/cli/result.ts +2 -0
- package/src/cli/series-search.ts +6 -1
- package/src/cli/ticker-resolution.ts +1 -2
- package/src/cli/unknown-command.ts +18 -0
- package/src/components/chart/composite/composite-chart.tsx +80 -19
- package/src/components/chart/composite/format.ts +17 -0
- package/src/components/chart/composite/legend.tsx +26 -15
- package/src/components/chart/composite/panel-labels.ts +38 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +25 -7
- package/src/components/chart/composite/price-axis-labels.tsx +16 -3
- package/src/components/chart/composite/scene.ts +60 -2
- package/src/components/chart/composite/series-axis-tags.tsx +67 -0
- package/src/components/chart/composite/tools.ts +2 -2
- package/src/components/chart/composite/types.ts +28 -0
- package/src/components/chart/core/view-reset.ts +46 -0
- package/src/components/command-bar/commands/collection.ts +1 -1
- package/src/components/command-bar/commands/direct/index.ts +48 -1
- package/src/components/command-bar/commands/registry.ts +8 -0
- package/src/components/command-bar/list/model.ts +7 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/panel/prompt-binding.ts +8 -0
- package/src/components/command-bar/routes/root/command-items.ts +17 -2
- package/src/components/command-bar/routes/root/pick-prefetch.ts +38 -0
- package/src/components/command-bar/routes/root/provider-search.ts +6 -0
- package/src/components/command-bar/routes/root/runtime.ts +3 -0
- package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -1
- package/src/components/command-bar/routes/root/shortcut-items.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/actions.ts +34 -0
- package/src/components/command-bar/surface/index.tsx +7 -1
- package/src/components/command-bar/workflow/collection-actions.ts +6 -5
- package/src/components/command-bar/workflow/ops.ts +8 -5
- package/src/components/command-bar/workflow/pane-reuse.ts +3 -2
- package/src/components/command-bar/workflow/tickers.ts +1 -1
- package/src/components/form-modal/confirm.tsx +1 -0
- package/src/components/form-modal/request.ts +2 -0
- package/src/components/layout/detached-pane-shell.tsx +6 -1
- package/src/components/layout/pane/content.tsx +11 -8
- package/src/components/layout/pane/sidebar.tsx +24 -11
- package/src/components/layout/pane/ticker-fate-gate.tsx +56 -0
- package/src/components/layout/shell/index.tsx +5 -1
- package/src/components/layout/shell/menu.ts +8 -0
- package/src/components/layout/shell/pane/capture.ts +29 -12
- package/src/components/onboarding/wizard-model.ts +1 -1
- package/src/components/onboarding/wizard-positions.ts +18 -10
- package/src/components/ticker/badge/format.ts +64 -15
- package/src/components/ticker/badge/index.tsx +32 -11
- package/src/components/ticker/badge/list.tsx +4 -3
- package/src/components/ticker/badge/text.tsx +2 -2
- package/src/components/ticker/list-table-view.tsx +8 -1
- package/src/components/ui/confirm-dialog.tsx +30 -4
- package/src/components/ui/data-table/opentui/index.tsx +61 -20
- package/src/components/ui/data-table/opentui/model.ts +104 -0
- package/src/components/ui/fields.tsx +5 -0
- package/src/components/ui/inline-quick-add.tsx +4 -1
- package/src/components/ui/query-bar-keys.tsx +29 -5
- package/src/components/ui/query-bar.tsx +29 -4
- package/src/components/ui/windowed-list.tsx +27 -0
- package/src/data/config/store/normalize.ts +8 -0
- package/src/docs/snapshot.json +248 -5
- package/src/i18n/es.ts +3 -1
- package/src/i18n/ja.ts +3 -1
- package/src/i18n/ko.ts +3 -1
- package/src/i18n/zh-cn.ts +3 -1
- package/src/i18n/zh-tw.ts +3 -1
- package/src/layout-marketplace/cloud.ts +16 -5
- package/src/layout-marketplace/gallery.tsx +4 -4
- package/src/layout-marketplace/linked-sync.ts +9 -3
- package/src/layout-marketplace/linked.ts +24 -3
- package/src/layout-marketplace/model.ts +39 -4
- package/src/market-data/coordinator/events.ts +5 -1
- package/src/market-data/coordinator/index.ts +38 -1
- package/src/market-data/coordinator/quotes.ts +4 -0
- package/src/market-data/futures-contract.ts +53 -0
- package/src/market-data/hooks.tsx +225 -30
- package/src/market-data/market/bar-period.ts +71 -0
- package/src/market-data/market/format.ts +14 -2
- package/src/market-data/market/freshness.ts +28 -1
- package/src/market-data/market/status.ts +152 -13
- package/src/market-data/model-dividend-yield.ts +76 -0
- package/src/market-data/quotes/freshness.ts +30 -1
- package/src/{plugins/builtin/portfolio-list → market-data/quotes}/live-valuation.ts +6 -6
- package/src/market-data/quotes/regular-session-valuation.ts +32 -1
- package/src/plugins/builtin/analytics/metrics.ts +8 -4
- package/src/plugins/builtin/analytics/risk-headless.ts +12 -5
- package/src/plugins/builtin/analytics/risk-model.ts +74 -15
- package/src/plugins/builtin/analytics/risk-pane.tsx +6 -6
- package/src/plugins/builtin/broker-manager/detail.tsx +2 -2
- package/src/plugins/builtin/broker-manager/model.ts +2 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +0 -1
- package/src/plugins/builtin/chart-composer/editor/controller.ts +14 -10
- package/src/plugins/builtin/chart-composer/formula.ts +233 -0
- package/src/plugins/builtin/chart-composer/headless.ts +27 -2
- package/src/plugins/builtin/chart-composer/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/pane.tsx +41 -10
- package/src/plugins/builtin/chart-composer/presets.ts +91 -2
- package/src/plugins/builtin/chart-composer/quick-add.tsx +9 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +44 -11
- package/src/plugins/builtin/chart-composer/series-expression.ts +1 -1
- package/src/plugins/builtin/chart-composer/settings.ts +16 -4
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/content/channel-navigation.ts +17 -6
- package/src/plugins/builtin/chat/content/footer.tsx +5 -2
- package/src/plugins/builtin/chat/content/index.tsx +12 -5
- package/src/plugins/builtin/chat/content/mentions.ts +50 -21
- package/src/plugins/builtin/chat/content/scroll.ts +12 -5
- package/src/plugins/builtin/chat/content/snapshot.ts +11 -0
- package/src/plugins/builtin/chat/content/transcript.tsx +48 -37
- package/src/plugins/builtin/chat/content/whole-message-copy.ts +44 -2
- package/src/plugins/builtin/chat/controller/index.ts +88 -17
- package/src/plugins/builtin/chat/controller/mention-candidates.ts +104 -0
- package/src/plugins/builtin/chat/controller/messages.ts +14 -2
- package/src/plugins/builtin/chat/controller/realtime.ts +9 -0
- package/src/plugins/builtin/chat/controller/state.ts +25 -0
- package/src/plugins/builtin/chat/controller/unread-people.ts +240 -0
- package/src/plugins/builtin/chat/controller/view.ts +4 -0
- package/src/plugins/builtin/chat/conversation-recency.ts +73 -0
- package/src/plugins/builtin/chat/conversations.ts +12 -5
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/layout.ts +23 -5
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
- package/src/plugins/builtin/chat/share/flow.tsx +18 -9
- package/src/plugins/builtin/chat/sidebar-rows.ts +18 -5
- package/src/plugins/builtin/chat/sidebar.tsx +6 -1
- package/src/plugins/builtin/chat/unread-badge.ts +38 -7
- package/src/plugins/builtin/cloud/askg/manifest.ts +4 -3
- package/src/plugins/builtin/cloud/askg/pane-attachment.ts +2 -3
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +16 -5
- package/src/plugins/builtin/commodities-energy/index.tsx +23 -4
- package/src/plugins/builtin/commodity-balances/client.ts +51 -193
- package/src/plugins/builtin/commodity-balances/headless.ts +10 -3
- package/src/plugins/builtin/commodity-balances/model.ts +5 -180
- package/src/plugins/builtin/commodity-balances/pane.tsx +3 -0
- package/src/plugins/builtin/custom-view/index.tsx +1 -1
- package/src/plugins/builtin/custom-view/loader.ts +57 -7
- package/src/plugins/builtin/custom-view/sources.ts +146 -0
- package/src/plugins/builtin/custom-view/view-spec.ts +37 -1
- package/src/plugins/builtin/earnings/board-model.ts +33 -3
- package/src/plugins/builtin/earnings/board-table.ts +59 -14
- package/src/plugins/builtin/earnings/board.tsx +36 -3
- package/src/plugins/builtin/earnings/headless.ts +24 -5
- package/src/plugins/builtin/earnings/history-model.ts +9 -1
- package/src/plugins/builtin/earnings/history-table.ts +16 -5
- package/src/plugins/builtin/earnings/history.tsx +40 -10
- package/src/plugins/builtin/earnings/index.tsx +3 -1
- package/src/plugins/builtin/earnings/no-earnings.ts +52 -0
- package/src/plugins/builtin/econ-statistics/detail.tsx +4 -1
- package/src/plugins/builtin/econ-statistics/headless.ts +5 -0
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +4 -0
- package/src/plugins/builtin/futures/model.ts +5 -4
- package/src/plugins/builtin/futures/series.ts +38 -1
- package/src/plugins/builtin/futures-curve/client.ts +4 -4
- package/src/plugins/builtin/futures-curve/headless.ts +33 -8
- package/src/plugins/builtin/futures-curve/index.tsx +4 -4
- package/src/plugins/builtin/futures-curve/model.ts +109 -6
- package/src/plugins/builtin/futures-curve/pane.tsx +24 -13
- package/src/plugins/builtin/insider/headless.ts +74 -4
- package/src/plugins/builtin/insider/index.tsx +99 -5
- package/src/plugins/builtin/insider/signals-client.ts +69 -0
- package/src/plugins/builtin/insider/signals-model.ts +146 -0
- package/src/plugins/builtin/insider/signals.tsx +176 -0
- package/src/plugins/builtin/iv-history/client.ts +41 -2
- package/src/plugins/builtin/iv-history/headless.ts +24 -16
- package/src/plugins/builtin/iv-history/screen-pane.tsx +25 -7
- package/src/plugins/builtin/jodi/client.ts +42 -205
- package/src/plugins/builtin/jodi/headless.ts +13 -4
- package/src/plugins/builtin/jodi/model.ts +18 -43
- package/src/plugins/builtin/jodi/pane.tsx +5 -0
- package/src/plugins/builtin/kelly-sizer/headless.ts +1 -1
- package/src/plugins/builtin/kelly-sizer/history.ts +5 -3
- package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
- package/src/plugins/builtin/macro-day/release-table.ts +3 -3
- package/src/plugins/builtin/market-movers/client.ts +32 -6
- package/src/plugins/builtin/market-movers/headless.ts +7 -2
- package/src/plugins/builtin/market-movers/model.ts +64 -3
- package/src/plugins/builtin/market-movers/screener.ts +20 -9
- package/src/plugins/builtin/market-valuation/detail.tsx +7 -2
- package/src/plugins/builtin/market-valuation/headless.ts +17 -4
- package/src/plugins/builtin/market-valuation/pane.tsx +7 -3
- package/src/plugins/builtin/market-valuation/view.ts +10 -0
- package/src/plugins/builtin/mna/model.ts +2 -1
- package/src/plugins/builtin/options-positioning/client.ts +10 -0
- package/src/plugins/builtin/options-positioning/headless.ts +88 -13
- package/src/plugins/builtin/options-positioning/index.tsx +2 -2
- package/src/plugins/builtin/options-positioning/model.ts +47 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +55 -26
- package/src/plugins/builtin/options-scenario/charts.tsx +13 -4
- package/src/plugins/builtin/options-scenario/client.ts +111 -17
- package/src/plugins/builtin/options-scenario/collar.ts +141 -0
- package/src/plugins/builtin/options-scenario/csp.ts +1 -1
- package/src/plugins/builtin/options-scenario/freshness.ts +27 -0
- package/src/plugins/builtin/options-scenario/headless.ts +28 -12
- package/src/plugins/builtin/options-scenario/index.tsx +1 -1
- package/src/plugins/builtin/options-scenario/model.ts +55 -5
- package/src/plugins/builtin/options-scenario/pane.tsx +24 -5
- package/src/plugins/builtin/perps/client.ts +77 -6
- package/src/plugins/builtin/perps/headless.ts +25 -14
- package/src/plugins/builtin/perps/index.tsx +1 -1
- package/src/plugins/builtin/perps/model.ts +32 -0
- package/src/plugins/builtin/perps/pane.tsx +5 -4
- package/src/plugins/builtin/portfolio-list/allocation.ts +9 -4
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +39 -20
- package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +15 -7
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +1 -1
- package/src/plugins/builtin/portfolio-list/column-values.ts +4 -3
- package/src/plugins/builtin/portfolio-list/command-bar.ts +1 -1
- package/src/plugins/builtin/portfolio-list/headless.ts +10 -3
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +36 -4
- package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +1 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +35 -6
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +2 -1
- package/src/plugins/builtin/portfolio-list/settings.ts +22 -0
- package/src/plugins/builtin/portfolio-list/since-last-seen.ts +197 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +14 -2
- package/src/plugins/builtin/rate-path/model.ts +1 -1
- package/src/plugins/builtin/rate-path/pane.tsx +2 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +14 -5
- package/src/plugins/builtin/research/event-model.ts +41 -49
- package/src/plugins/builtin/research/event-sources.ts +1 -1
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +6 -0
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
- package/src/plugins/builtin/reverse-dcf/headless.ts +25 -9
- package/src/plugins/builtin/reverse-dcf/index.tsx +4 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +39 -5
- package/src/plugins/builtin/reverse-dcf/pane.tsx +16 -6
- package/src/plugins/builtin/sectors/client.ts +46 -19
- package/src/plugins/builtin/sectors/sector-model.ts +22 -10
- package/src/plugins/builtin/shared/earnings-report.ts +48 -1
- package/src/plugins/builtin/shared/history-span.ts +55 -0
- package/src/plugins/builtin/shared/report-freshness.ts +20 -11
- package/src/plugins/builtin/shared/series-detail-chart.tsx +5 -2
- package/src/plugins/builtin/shared/series-list-detail.tsx +7 -3
- package/src/plugins/builtin/shared/ticker-surface.ts +16 -2
- package/src/plugins/builtin/shared/use-quote-board.ts +12 -5
- package/src/plugins/builtin/themes/headless.ts +15 -2
- package/src/plugins/builtin/thirteenf/api.ts +60 -43
- package/src/plugins/builtin/thirteenf/data.ts +76 -35
- package/src/plugins/builtin/thirteenf/headless.ts +2 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +10 -2
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +85 -17
- package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +92 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +26 -8
- package/src/plugins/builtin/ticker-detail/headless-schema.ts +4 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +95 -22
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +17 -3
- package/src/plugins/builtin/yield-curve/curves.ts +15 -1
- package/src/plugins/builtin/yield-curve/headless.ts +17 -7
- package/src/plugins/builtin/yield-curve/history.ts +2 -0
- package/src/plugins/builtin/yield-curve/index.tsx +23 -18
- package/src/plugins/builtin/yield-curve/series.ts +19 -9
- package/src/plugins/builtin/yield-curve/treasury-data.ts +2 -0
- package/src/plugins/builtin/yield-curve/world.ts +4 -0
- package/src/plugins/registry/host-actions.ts +3 -0
- package/src/{plugins/builtin/portfolio-list → portfolio}/mutations.ts +6 -6
- package/src/portfolio/valuation-basis.ts +4 -21
- package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
- package/src/remote/controller.ts +27 -7
- package/src/remote/schema.ts +25 -9
- package/src/renderers/browser/app-services.ts +7 -1
- package/src/renderers/browser/config-host.ts +55 -2
- package/src/renderers/browser/first-screen.ts +193 -0
- package/src/renderers/browser/main.tsx +59 -7
- package/src/renderers/cloudflare/http-proxy.ts +21 -13
- package/src/renderers/dom/desktop/controls.tsx +2 -0
- package/src/renderers/dom/desktop/query-bar.tsx +1 -1
- package/src/renderers/dom/dom-ui-host.tsx +2 -0
- package/src/renderers/dom/windowed-list.tsx +201 -0
- package/src/renderers/dom/windowed-rows.ts +65 -0
- package/src/renderers/electrobun/bun/application-menu/index.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +3 -7
- package/src/renderers/electrobun/bun/desktop/http-stream.ts +21 -6
- package/src/renderers/electrobun/bun/desktop/update.ts +132 -14
- package/src/renderers/electrobun/shared/protocol.ts +7 -4
- package/src/renderers/electrobun/view/http-fetch.ts +12 -1
- package/src/renderers/electrobun/view/update-host.ts +2 -2
- package/src/shares/bug-report-dialog.tsx +162 -0
- package/src/shares/bug-report-flow.tsx +84 -0
- package/src/shares/bug-report.ts +42 -0
- package/src/shares/capture-flow.ts +19 -0
- package/src/shares/capture-preview.tsx +129 -0
- package/src/shares/chat.ts +10 -1
- package/src/shares/dialog.tsx +16 -91
- package/src/shares/flow.tsx +12 -16
- package/src/shares/personal-pane.ts +35 -1
- package/src/sources/listing-symbols.ts +8 -4
- package/src/state/app/context/index.tsx +13 -0
- package/src/state/app/tab-cursor.ts +85 -0
- package/src/sync/core-contributors.ts +206 -59
- package/src/telemetry/usage-counts.ts +16 -0
- package/src/test-support/config-store.ts +31 -0
- package/src/test-support/moved-symbols.ts +64 -0
- package/src/theme/colors.ts +30 -0
- package/src/tickers/listing-fate.ts +242 -0
- package/src/tickers/open-target.ts +61 -11
- package/src/tickers/search/index.ts +45 -3
- package/src/tickers/search/ranking.ts +19 -2
- package/src/tickers/search/types.ts +3 -0
- package/src/tickers/search/upsert.ts +22 -7
- package/src/tickers/ticker-changes.ts +26 -9
- package/src/time-series/bar-spacing.ts +19 -0
- package/src/time-series/chart-data.ts +37 -13
- package/src/time-series/field-catalog.ts +5 -2
- package/src/time-series/forward-valuation.ts +10 -7
- package/src/time-series/fundamentals.ts +49 -11
- package/src/time-series/hooks.ts +2 -0
- package/src/time-series/live-bars.ts +3 -1
- package/src/time-series/parsed-history-cache.ts +2 -0
- package/src/time-series/price-units.ts +69 -0
- package/src/time-series/resolve.ts +71 -11
- package/src/time-series/session-bar.ts +138 -0
- package/src/time-series/spec.ts +20 -3
- package/src/time-series/studies.ts +211 -12
- package/src/time-series/transforms.ts +2 -0
- package/src/time-series/types.ts +11 -1
- package/src/time-series/valuation-price.ts +13 -0
- package/src/types/config.ts +24 -1
- package/src/types/financials.ts +2 -0
- package/src/types/headless.ts +27 -0
- package/src/types/plugin.ts +7 -0
- package/src/ui/host.tsx +5 -0
- package/src/updater/channel.ts +51 -0
- package/src/updater/github-releases.ts +85 -16
- package/src/updater/index.ts +128 -17
- package/src/utils/asset-category.ts +20 -0
- package/src/utils/datetime-format.ts +7 -1
- package/src/utils/decimal-comma.ts +83 -3
- package/src/utils/fundamentals.ts +7 -2
- package/src/utils/http-proxy-response.ts +66 -9
- package/src/utils/plugin-proxy-hosts.json +1 -5
- package/src/utils/semver.ts +47 -2
- package/src/utils/typo-distance.ts +22 -0
- package/src/plugins/builtin/shared/zip-text.ts +0 -46
|
@@ -16,10 +16,12 @@ import {
|
|
|
16
16
|
getExtendedSessionDisplay,
|
|
17
17
|
getQuoteSessionFields,
|
|
18
18
|
getRegularSessionDisplay,
|
|
19
|
+
quoteHeadlineTime,
|
|
19
20
|
quoteSessionLabel,
|
|
21
|
+
quoteSessionStateLabel,
|
|
20
22
|
type ExtendedSession,
|
|
21
23
|
} from "../../market-data/market/status";
|
|
22
|
-
import { formatCompact, formatDistributionAmount, formatPercent, formatPercentRaw } from "../../utils/format";
|
|
24
|
+
import { formatCompact, formatCurrency, formatDistributionAmount, formatNumber, formatPercent, formatPercentRaw } from "../../utils/format";
|
|
23
25
|
import { withCliServices, withMarketData } from "../context";
|
|
24
26
|
import { isoDate, parsePositiveInt, requireOneArg, takeFlag, takeOption } from "./command-utils";
|
|
25
27
|
import type { BuiltinCliCommandDef } from "../command-options";
|
|
@@ -59,6 +61,7 @@ import {
|
|
|
59
61
|
formatOptionIvCell,
|
|
60
62
|
formatOptionQuoteCell,
|
|
61
63
|
OPTION_MODEL_RATE,
|
|
64
|
+
OPTION_VOLATILITY_FIELDS,
|
|
62
65
|
OPTIONS_USAGE,
|
|
63
66
|
optionRows,
|
|
64
67
|
optionsBasisFields,
|
|
@@ -81,6 +84,7 @@ import {
|
|
|
81
84
|
type CliReportTables,
|
|
82
85
|
} from "../report-tables";
|
|
83
86
|
import {
|
|
87
|
+
formingPeriodBar,
|
|
84
88
|
historyBarStatusNote,
|
|
85
89
|
historyCoverageWarning,
|
|
86
90
|
historyFacts,
|
|
@@ -102,6 +106,7 @@ import { bareFuturesCode } from "../../market-data/futures-board";
|
|
|
102
106
|
import { newsEmptyMessage } from "./news-hints";
|
|
103
107
|
import { newsFallbackText, newsSearchHeading, newsSearchText, newsSearchTextProblem, searchNewsStories } from "./news-search";
|
|
104
108
|
import { formatUtcTime } from "../../utils/utc-time";
|
|
109
|
+
import { DAY_SO_FAR_TEXT, withSessionBar } from "../../time-series/session-bar";
|
|
105
110
|
import { isFiniteNumber } from "../../utils/guards";
|
|
106
111
|
import { exportedFundamentals } from "../../utils/price-earnings";
|
|
107
112
|
import { fundamentalsFreshness, fxPairFreshness, quotesFreshness, rowsFreshness, valuationFreshness } from "../freshness";
|
|
@@ -116,7 +121,15 @@ import {
|
|
|
116
121
|
SEC_FILINGS,
|
|
117
122
|
} from "../../plugins/builtin/shared/report-freshness";
|
|
118
123
|
import {
|
|
119
|
-
|
|
124
|
+
earningsDateStatus,
|
|
125
|
+
earningsDetailParts,
|
|
126
|
+
earningsEpsBasis,
|
|
127
|
+
epsBasisName,
|
|
128
|
+
isProvisionalActual,
|
|
129
|
+
quarterLabel,
|
|
130
|
+
showsEpsBasis,
|
|
131
|
+
} from "../../plugins/builtin/shared/earnings-report";
|
|
132
|
+
import {
|
|
120
133
|
tradingBoardNote,
|
|
121
134
|
EXCHANGE_OPTION,
|
|
122
135
|
futuresTryLine,
|
|
@@ -132,16 +145,15 @@ import {
|
|
|
132
145
|
loadForListing,
|
|
133
146
|
loadListingQuote,
|
|
134
147
|
listingFateLoader,
|
|
135
|
-
|
|
136
|
-
noLongerTradingNote,
|
|
148
|
+
movedSymbolLine,
|
|
137
149
|
notTradedMessage,
|
|
138
150
|
quoteBareListing,
|
|
139
151
|
requireCliListing,
|
|
140
|
-
tradingListing,
|
|
141
152
|
type CliListing,
|
|
142
153
|
type ListingDataRequest,
|
|
143
154
|
type ListingIdentity,
|
|
144
155
|
} from "../listing-arg";
|
|
156
|
+
import { bareListingNote, listingFateNotes, noLongerTradingNote, tradingListing } from "../../tickers/listing-fate";
|
|
145
157
|
import { indexRootAlsoLine, indexRootFor, indexRootTryLine, isYieldIndexSymbol } from "../index-roots";
|
|
146
158
|
import { earningsCalendarFailure, earningsChoiceNote, earningsDateLabel, isListingAbroad, noListingCalendarNote } from "../earnings-calendar";
|
|
147
159
|
import { formatBasisPoints, toBasisPoints } from "../../utils/basis-points";
|
|
@@ -155,11 +167,11 @@ import { secRegistrantMismatchMessage, SecRegistrantMismatchError, areDifferentC
|
|
|
155
167
|
import { isCryptoPairSymbol } from "../../utils/crypto-pair";
|
|
156
168
|
import { looksLikeCurrencyPair } from "../../utils/currency-pair";
|
|
157
169
|
import { canonicalExchange, isUsListingExchange } from "../../utils/exchanges";
|
|
158
|
-
import { describeTickerChange, startTickerChangeLookup, tickerChangeLine, type TickerChange } from "../../tickers/ticker-changes";
|
|
170
|
+
import { describeTickerChange, movedAwayChange, startTickerChangeLookup, tickerChangeLine, type TickerChange } from "../../tickers/ticker-changes";
|
|
159
171
|
import { noSecReportsNotice, nonUsSecListingVenue } from "../../utils/sec";
|
|
160
172
|
import type { MarketContext } from "../types";
|
|
161
173
|
import { windowRows } from "../row-window";
|
|
162
|
-
import { crossRate, describeFxRate, isUsableRate,
|
|
174
|
+
import { crossRate, describeFxConversion, describeFxRate, FX_USAGE, isUsableRate, parseFxArgs } from "../fx-pair";
|
|
163
175
|
import {
|
|
164
176
|
holderListFacts,
|
|
165
177
|
holderListPeriod,
|
|
@@ -271,7 +283,8 @@ const QUOTE_LEAD_COLUMNS = [
|
|
|
271
283
|
extendedColumn("PRE"),
|
|
272
284
|
extendedColumn("POST"),
|
|
273
285
|
extendedColumn("OVERNIGHT"),
|
|
274
|
-
|
|
286
|
+
// An export keeps the market state (PRE-MKT); the table names the close a pre-market with no trade stands at (Thu close).
|
|
287
|
+
{ key: "session", header: "Session", shrink: false, format: (_value: unknown, row: QuoteRow) => row.sessionLabel },
|
|
275
288
|
];
|
|
276
289
|
|
|
277
290
|
type QuoteRow = ReturnType<typeof quoteRows>[number];
|
|
@@ -393,7 +406,8 @@ function quoteRows(results: QuoteCliRecord[], now = Date.now()) {
|
|
|
393
406
|
extendedChangePercent: roundedPercent(extended?.changePercent),
|
|
394
407
|
// The extended price is the session's bid and ask midpoint, nothing having traded in it.
|
|
395
408
|
extendedMark: extended?.mark === true,
|
|
396
|
-
session:
|
|
409
|
+
session: quoteSessionStateLabel(quote, now) ?? "",
|
|
410
|
+
sessionLabel: quoteSessionLabel(quote, now) ?? "",
|
|
397
411
|
// The close the shown move is measured from: the one before the session Last is.
|
|
398
412
|
previousClose: price(display?.change != null ? display.price - display.change : quote?.previousClose),
|
|
399
413
|
dayRange: quote?.low != null && quote.high != null
|
|
@@ -404,7 +418,8 @@ function quoteRows(results: QuoteCliRecord[], now = Date.now()) {
|
|
|
404
418
|
providerId: quote?.providerId ?? "",
|
|
405
419
|
source: quote?.dataSource ?? quote?.providerId ?? "",
|
|
406
420
|
feed: quoteFeedState(quote, now),
|
|
407
|
-
|
|
421
|
+
// When the price Last shows was observed: in a pre-market nothing has traded in, the close it stands at.
|
|
422
|
+
updatedAt: quote?.lastUpdated ? new Date(quoteHeadlineTime(quote)).toISOString() : "",
|
|
408
423
|
error: result.error ?? "",
|
|
409
424
|
};
|
|
410
425
|
});
|
|
@@ -490,19 +505,32 @@ function corporateActionRows(data: CorporateActionsData) {
|
|
|
490
505
|
// An upcoming estimate carries no unit of its own; it shares the one every reported quarter states.
|
|
491
506
|
const reportedCurrencies = new Set(data.earnings.map((event) => event.currency?.trim()).filter(Boolean));
|
|
492
507
|
const earningsCurrency = reportedCurrencies.size === 1 ? [...reportedCurrencies][0] : undefined;
|
|
508
|
+
// To the cent, as ERN and EE show it; a consensus average carries more digits.
|
|
493
509
|
const eps = (value: number | undefined, unit: string | undefined) => (
|
|
494
|
-
value == null || !
|
|
495
|
-
:
|
|
510
|
+
value == null || !Number.isFinite(value) ? "-"
|
|
511
|
+
: !unit ? formatNumber(value, 2)
|
|
512
|
+
: /^[A-Z]{3}$/.test(unit) ? formatCurrency(value, unit, 2) : `${formatNumber(value, 2)} ${unit}`
|
|
496
513
|
);
|
|
497
514
|
return [
|
|
498
515
|
...data.earnings.map((event) => {
|
|
499
516
|
const unit = event.currency?.trim() || earningsCurrency;
|
|
517
|
+
const quarter = quarterLabel(event.fiscalPeriod);
|
|
518
|
+
const basis = earningsEpsBasis(event.epsBasis);
|
|
519
|
+
// History rows are keyed by fiscal quarter end, upcoming ones by announcement date.
|
|
520
|
+
const note = [event.dateType === "fiscal-period-end" ? "period end" : null, quarter === "-" ? null : quarter].filter(Boolean).join(", ");
|
|
500
521
|
return {
|
|
501
522
|
type: "earnings",
|
|
502
523
|
date: event.date,
|
|
503
|
-
//
|
|
504
|
-
detail:
|
|
505
|
-
|
|
524
|
+
// The basis, beat, date status and analysts are the words EE uses for the same row.
|
|
525
|
+
detail: [event.epsActual == null ? `est ${eps(event.epsEstimate, unit)}` : `eps ${eps(event.epsActual, unit)}`,
|
|
526
|
+
...earningsDetailParts(event)].join(", ") + (note ? ` (${note})` : ""),
|
|
527
|
+
epsActual: event.epsActual,
|
|
528
|
+
epsEstimate: event.epsEstimate,
|
|
529
|
+
epsBasis: showsEpsBasis({ epsActual: event.epsActual, epsBasis: basis }) ? epsBasisName(basis) : undefined,
|
|
530
|
+
...(isProvisionalActual(event) ? { epsProvisional: true } : {}),
|
|
531
|
+
epsAnalysts: event.epsAnalysts,
|
|
532
|
+
dateStatus: earningsDateStatus(event.dateStatus) ?? undefined,
|
|
533
|
+
fiscalPeriod: event.fiscalPeriod,
|
|
506
534
|
};
|
|
507
535
|
}),
|
|
508
536
|
...data.dividends.map((event) => ({
|
|
@@ -562,6 +590,17 @@ function earningsRows(events: EarningsEvent[]) {
|
|
|
562
590
|
/** Symbols at most whose venues a command looks up to name the company each bare one went to. */
|
|
563
591
|
const MAX_CHOICE_LOOKUPS = 10;
|
|
564
592
|
|
|
593
|
+
/**
|
|
594
|
+
* The line `quote` prints beside a symbol that changed, as `history` and
|
|
595
|
+
* `ticker` word it: a company that moved, with nothing new listed there,
|
|
596
|
+
* names the quote of its new symbol ("HFC became DINO on 2022-03-15:
|
|
597
|
+
* `gloomberb quote DINO`"); its old line's last price stays in the row.
|
|
598
|
+
*/
|
|
599
|
+
function quoteChangeLine(change: TickerChange | null | undefined): string | null {
|
|
600
|
+
const moved = movedAwayChange(change);
|
|
601
|
+
return moved ? movedSymbolLine(moved, `gloomberb quote ${moved.tradesAs}`) : tickerChangeLine(change);
|
|
602
|
+
}
|
|
603
|
+
|
|
565
604
|
async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1], commandName: "quote" | "compare") {
|
|
566
605
|
const args = [...rawArgs];
|
|
567
606
|
const exchange = takeOption(args, "--exchange") ?? "";
|
|
@@ -600,7 +639,7 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
|
|
|
600
639
|
}));
|
|
601
640
|
const failed = answers[0]?.failure;
|
|
602
641
|
if (listings.length === 1 && failed) {
|
|
603
|
-
ctx.fail(failed.message, [
|
|
642
|
+
ctx.fail(failed.message, [quoteChangeLine(tickerChanges[0]), failed.details].filter(Boolean).join("\n") || undefined);
|
|
604
643
|
}
|
|
605
644
|
// A futures quote adds its dated settlement and contract terms, as `ticker` prints them; `compare` stays a table.
|
|
606
645
|
const contracts = await Promise.all(answers.map(({ result }) => commandName === "quote" && result.quote
|
|
@@ -632,7 +671,7 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
|
|
|
632
671
|
const notes = [
|
|
633
672
|
// FB now lists another security; the company that was FB trades as META. A symbol a
|
|
634
673
|
// company renamed into (META) is what was typed: its change is in the row, not here.
|
|
635
|
-
...tickerChanges.flatMap((change) =>
|
|
674
|
+
...tickerChanges.flatMap((change) => quoteChangeLine(change) ?? []),
|
|
636
675
|
// Which listing a bare symbol was read as, and the index a bare root is not, are in no row.
|
|
637
676
|
...answers.flatMap((answer) => answer.notes),
|
|
638
677
|
// A row that stopped trading long ago reads as a quote that is merely stale.
|
|
@@ -839,7 +878,10 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
839
878
|
.catch((error) => failHistory(error, listing.key, ctx)),
|
|
840
879
|
quoted,
|
|
841
880
|
]);
|
|
842
|
-
const {
|
|
881
|
+
const { resolution } = loaded;
|
|
882
|
+
// While the session trades the day's bar is the quote's, whatever range the history was cut for (withSessionBar).
|
|
883
|
+
const points = withSessionBar(loaded.points, quote, { now: Date.now(), venue: quote?.listingExchangeName || exchange || undefined });
|
|
884
|
+
const fromQuote = points !== loaded.points;
|
|
843
885
|
const identity = listingIdentity(listing, quote);
|
|
844
886
|
// Without a quote currency, the listing metadata research reads (as `ticker` does) may still state it.
|
|
845
887
|
const listed = quote?.currency ? quote : await market.dataProvider.getQuoteMetadata?.(symbol, exchange).catch(() => null);
|
|
@@ -849,20 +891,28 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
849
891
|
};
|
|
850
892
|
// A coin's volume is traded value in its quote currency: said in every row and on the status line.
|
|
851
893
|
const volume = historyVolume(unit, traded);
|
|
852
|
-
const
|
|
894
|
+
const served = historyRows(points, resolution, unit.currency, unit.unit, volume);
|
|
895
|
+
// Bars stop arriving on the listing's own trading days, not New York's.
|
|
896
|
+
const venue = quote?.listingExchangeName || exchange || undefined;
|
|
897
|
+
// October's monthly bar on 16 Oct runs through the 15th: marked partial, and the status line says how far.
|
|
898
|
+
const forming = formingPeriodBar(served, venue);
|
|
899
|
+
const data = forming?.rows ?? served;
|
|
853
900
|
const interval = data[0]?.interval ?? null;
|
|
854
901
|
const decimals = historyPriceDecimals(data, listing.saved?.metadata.assetCategory);
|
|
855
902
|
const price = (value: unknown) => typeof value === "number" ? value.toFixed(decimals) : "";
|
|
856
903
|
// Intraday bars print in UTC, as the charts and time and sales label them, not the host zone.
|
|
857
904
|
const intraday = data.some((row) => row.date.length > 10);
|
|
858
905
|
// A bar history: dated by its last bar, stale once bars of its size stop arriving.
|
|
906
|
+
// The day's own bar, while its session trades, is not a close: dated by the quote it was read from.
|
|
907
|
+
const dayToDate = fromQuote && interval === "1d" && data.at(-1)?.partial === true;
|
|
859
908
|
const freshness = rowsFreshness(data, {
|
|
860
909
|
...barHistoryFreshness(intraday ? null : barResolutionFromDates(data.map((row) => row.date))),
|
|
861
910
|
observedKey: "date",
|
|
862
911
|
oldest: null,
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
...(
|
|
912
|
+
cadenceExchange: venue,
|
|
913
|
+
...(forming ? { periodToDate: forming.periodToDate } : {}),
|
|
914
|
+
...(dayToDate ? { basis: "daily bars", ...(quote && Number.isFinite(quote.lastUpdated) ? { asOf: new Date(quote.lastUpdated).toISOString() } : {}) } : {}),
|
|
915
|
+
...(volume || dayToDate ? { note: [volume ? historyVolumeText(volume, interval) : null, dayToDate ? `the newest bar is ${DAY_SO_FAR_TEXT}` : null].filter(Boolean).join("; ") } : {}),
|
|
866
916
|
});
|
|
867
917
|
const gaps = historyGapNote(data, traded);
|
|
868
918
|
const notes = [
|
|
@@ -870,7 +920,8 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
870
920
|
...listingFateNotes(await fate(), { served: data.length > 0, firstDate: data[0]?.date }),
|
|
871
921
|
...await listingChoiceNotes(listing, market, quote, venues),
|
|
872
922
|
...historyNotes(range, interval, data[0]?.date), historyCoverageWarning(range, loaded) ?? [],
|
|
873
|
-
|
|
923
|
+
// The status line says how far a bar still forming runs; the note is for the bars it does not name.
|
|
924
|
+
gaps ?? [], historyJumpNote(data) ?? [], historyFlagNote(data) ?? [], historyBarStatusNote(forming ? served : data) ?? [],
|
|
874
925
|
].flat();
|
|
875
926
|
const priceColumns: CliResultColumn[] = [
|
|
876
927
|
intraday
|
|
@@ -1627,6 +1678,8 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1627
1678
|
interval: OPTIONS_EXPORT_INTERVAL,
|
|
1628
1679
|
// `iv` is annualised volatility and `delta` the option's change per 1.00 move in the underlying, both decimals.
|
|
1629
1680
|
units: { iv: "annualized decimal", delta: "decimal per 1.00 move in the underlying" },
|
|
1681
|
+
// What each contract's `iv` and `impliedVolatility` are, so neither is read as the other.
|
|
1682
|
+
fields: OPTION_VOLATILITY_FIELDS,
|
|
1630
1683
|
// What `iv` and `delta` on each contract are valued from; the provider's own impliedVolatility is left as sent.
|
|
1631
1684
|
model: {
|
|
1632
1685
|
spot: basisFields.spot, spotBasis: basisFields.spotBasis, spotAsOf: basisFields.spotAsOf,
|
|
@@ -1685,13 +1738,12 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1685
1738
|
/** What an options chain export's `interval` says: one quote snapshot, not bars. */
|
|
1686
1739
|
const OPTIONS_EXPORT_INTERVAL = "snapshot";
|
|
1687
1740
|
|
|
1688
|
-
const FX_USAGE = "fx <currency> | fx <base>/<quote>";
|
|
1689
|
-
|
|
1690
1741
|
async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
1691
|
-
|
|
1742
|
+
if (rawArgs.length === 0) ctx.fail(`Usage: gloomberb ${FX_USAGE}`);
|
|
1692
1743
|
await withMarketData(ctx, async (market) => {
|
|
1693
|
-
const
|
|
1694
|
-
if ("error" in
|
|
1744
|
+
const parsed = parseFxArgs(rawArgs, market.config.baseCurrency);
|
|
1745
|
+
if ("error" in parsed) return ctx.fail(parsed.error, `Usage: gloomberb ${FX_USAGE}`);
|
|
1746
|
+
const { request, amount, toUsdByDefault } = parsed;
|
|
1695
1747
|
const { currency, baseCurrency } = request;
|
|
1696
1748
|
const pair = `${currency}/${baseCurrency}`;
|
|
1697
1749
|
const reasons = new Map<string, string>();
|
|
@@ -1719,7 +1771,8 @@ async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]
|
|
|
1719
1771
|
const observed = legs.flatMap((leg) => leg?.asOf ?? []);
|
|
1720
1772
|
const asOf = observed.length > 0 ? new Date(Math.min(...observed)).toISOString() : null;
|
|
1721
1773
|
const stale = legs.some((leg) => leg != null && (leg.staleAt <= Date.now() || leg.refreshError != null));
|
|
1722
|
-
const
|
|
1774
|
+
const converted = amount == null ? null : amount * rate;
|
|
1775
|
+
const row = { currency, baseCurrency, rate, asOf, stale, pair, inverse: 1 / rate, ...(amount == null ? {} : { amount, converted }) };
|
|
1723
1776
|
// The closing line dates the rate, and says when it is stale, as `fn FXC` does.
|
|
1724
1777
|
const freshness = fxPairFreshness(new Map(codes.flatMap((code, index) => {
|
|
1725
1778
|
const leg = legs[index];
|
|
@@ -1728,8 +1781,13 @@ async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]
|
|
|
1728
1781
|
timing: { asOf: leg.asOf, staleAt: leg.staleAt, error: leg.refreshError, delayMinutes: leg.delayMinutes },
|
|
1729
1782
|
}] as const] : [];
|
|
1730
1783
|
})));
|
|
1731
|
-
|
|
1732
|
-
|
|
1784
|
+
// An amount with one currency names no target; the answer says it went to USD.
|
|
1785
|
+
const warnings = toUsdByDefault && amount != null
|
|
1786
|
+
? [`No target currency given, so ${currency} is converted to USD; for another, write fx ${rawArgs[0]!.trim()} ${currency}/<to>.`]
|
|
1787
|
+
: undefined;
|
|
1788
|
+
ctx.printResult({ data: [row], freshness, warnings }, {
|
|
1789
|
+
// The conversion, then the rate it used; the source line under it dates that rate.
|
|
1790
|
+
text: () => amount == null ? describeFxRate(request, rate) : `${describeFxConversion(request, amount, rate)}\n${describeFxRate(request, rate)}`,
|
|
1733
1791
|
// The rate is the base currency's price of one unit of the other, at the latest quote of each leg.
|
|
1734
1792
|
exportIdentity: { symbolOrSeries: pair, currencyOrUnit: baseCurrency, interval: "spot" },
|
|
1735
1793
|
columns: [
|
|
@@ -1740,6 +1798,10 @@ async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]
|
|
|
1740
1798
|
{ key: "stale", header: "Stale" },
|
|
1741
1799
|
{ key: "pair", header: "Pair" },
|
|
1742
1800
|
{ key: "inverse", header: "Inverse", align: "right" },
|
|
1801
|
+
...(amount == null ? [] : [
|
|
1802
|
+
{ key: "amount", header: "Amount", align: "right" as const },
|
|
1803
|
+
{ key: "converted", header: "Converted", align: "right" as const },
|
|
1804
|
+
]),
|
|
1743
1805
|
],
|
|
1744
1806
|
});
|
|
1745
1807
|
});
|
|
@@ -2097,18 +2159,23 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
2097
2159
|
},
|
|
2098
2160
|
{
|
|
2099
2161
|
name: "fx",
|
|
2100
|
-
description: "Convert a currency into your base currency,
|
|
2162
|
+
description: "Convert a currency into your base currency, quote a pair such as USD/NGN, or convert an amount such as fx 1234.5 EUR/USD",
|
|
2101
2163
|
help: {
|
|
2102
2164
|
group: CLI_COMMAND_GROUPS.markets,
|
|
2103
|
-
usage: ["fx <currency>", "fx <base>/<quote>"],
|
|
2165
|
+
usage: ["fx <currency>", "fx <base>/<quote>", "fx <amount> <from>/<to>", "fx <amount> <from> <to>"],
|
|
2104
2166
|
sections: [{
|
|
2105
2167
|
title: "Direction",
|
|
2106
2168
|
lines: [
|
|
2107
2169
|
"A bare code is one unit of it in your base currency. A pair reads as markets quote it: USD/NGN is how many NGN one USD buys. Two codes neither of which is USD, such as ZAR/NGN, cross through their USD rates.",
|
|
2108
2170
|
"The answer states both directions: 1 NGN = 0.000752791 USD (USD/NGN 1328.39). --json, --csv and --ndjson carry rate (base currency per currency), pair, inverse, asOf and stale.",
|
|
2109
2171
|
],
|
|
2172
|
+
}, {
|
|
2173
|
+
title: "Amounts",
|
|
2174
|
+
lines: [
|
|
2175
|
+
"An amount in front converts it from the first currency to the second: fx 1234.5 EUR/USD, or fx 1234,5 EUR USD. It takes either decimal mark (1.234,5 and 1,234.5 are 1234.5) and reads as an amount of the first currency: 1,234 or 1.234 EUR are thousands, a third decimal being below a cent, while in a currency with three decimals (KWD) fx asks which. An amount and one currency converts to USD. The answer gives the converted amount, then the rate it used, dated by the source line; --json adds amount and converted.",
|
|
2176
|
+
],
|
|
2110
2177
|
}],
|
|
2111
|
-
examples: ["fx EUR", "fx NGN", "fx USD/NGN", "fx ZAR/NGN", "fx JPY --json"],
|
|
2178
|
+
examples: ["fx EUR", "fx NGN", "fx USD/NGN", "fx ZAR/NGN", "fx 1234.5 EUR/USD", "fx 1234,5 EUR USD", "fx JPY --json"],
|
|
2112
2179
|
},
|
|
2113
2180
|
execute: runFx,
|
|
2114
2181
|
},
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "../../plugins/builtin/options/open-interest-as-of";
|
|
14
14
|
import { DEFAULT_OPTION_CALC_DRAFT } from "../../plugins/builtin/options-calculator/model";
|
|
15
15
|
import { expiryIsoDate, parseOptionExpiration } from "../../utils/option-expiry";
|
|
16
|
+
import { loadModelDividendYield } from "../../market-data/model-dividend-yield";
|
|
16
17
|
|
|
17
18
|
export const OPTIONS_USAGE = "options <symbol> [--expiration <YYYY-MM-DD|unix>]";
|
|
18
19
|
|
|
@@ -66,6 +67,14 @@ interface OptionModelFigures {
|
|
|
66
67
|
/** The risk-free rate every options valuation in the app assumes. */
|
|
67
68
|
export const OPTION_MODEL_RATE = DEFAULT_OPTION_CALC_DRAFT.rate;
|
|
68
69
|
|
|
70
|
+
/** What each contract's two volatilities are, stated in the chain's JSON metadata so neither is read as the other. */
|
|
71
|
+
export const OPTION_VOLATILITY_FIELDS = {
|
|
72
|
+
iv: "Annualized volatility (decimal) solved from the quote midpoint, one per strike from its out-of-the-money side, at the "
|
|
73
|
+
+ "spot, valuation time and rate in metadata.model. Delta here and dealer gamma (GEX) are valued with it.",
|
|
74
|
+
impliedVolatility: "The data provider's own reported implied volatility, passed through unchanged. Delta does not use it; "
|
|
75
|
+
+ "GEX falls back to it only at a strike with no two-sided quote on either side.",
|
|
76
|
+
} as const;
|
|
77
|
+
|
|
69
78
|
/**
|
|
70
79
|
* IV and delta per contract, from the same solver and Greeks as the options
|
|
71
80
|
* pane (put-call parity forward, one volatility per strike, the pane's rate).
|
|
@@ -110,11 +119,13 @@ export function loadUnderlyingFinancials(
|
|
|
110
119
|
|
|
111
120
|
/**
|
|
112
121
|
* Spot and dividend yield for IV and delta, read from the underlying the way the
|
|
113
|
-
* options pane does; missing ones stay missing.
|
|
114
|
-
*
|
|
122
|
+
* options pane does; missing ones stay missing. The dividend yield is any the
|
|
123
|
+
* fundamentals state, stale or not, else, as OSA reads it
|
|
124
|
+
* (`loadModelDividendYield`), 0 for a history with no dividend paid.
|
|
125
|
+
* `financials` shares one read of them with a caller that needs more of them.
|
|
115
126
|
*/
|
|
116
127
|
export async function loadOptionModelInputs(
|
|
117
|
-
dataProvider: Pick<DataProvider, "getTickerFinancials">,
|
|
128
|
+
dataProvider: Pick<DataProvider, "getTickerFinancials" | "getCorporateActions">,
|
|
118
129
|
listing: CliListing,
|
|
119
130
|
quote: Promise<Quote | null>,
|
|
120
131
|
refresh: boolean,
|
|
@@ -123,10 +134,12 @@ export async function loadOptionModelInputs(
|
|
|
123
134
|
const financials = await underlying;
|
|
124
135
|
const current = [await quote, financials?.quote]
|
|
125
136
|
.find((candidate) => candidate && candidate.stale !== true && isFiniteNumber(candidate.price) && candidate.price > 0);
|
|
126
|
-
const
|
|
137
|
+
const { symbol, exchange } = listing.request;
|
|
138
|
+
const dividend = await loadModelDividendYield(financials?.fundamentals?.dividendYield, symbol,
|
|
139
|
+
dataProvider.getCorporateActions ? () => dataProvider.getCorporateActions!(symbol, exchange) : null);
|
|
127
140
|
return {
|
|
128
141
|
...(current ? { spot: current.price, quote: current } : {}),
|
|
129
|
-
...(
|
|
142
|
+
...(dividend ? { dividendYield: dividend.value } : {}),
|
|
130
143
|
};
|
|
131
144
|
}
|
|
132
145
|
|
|
@@ -180,7 +193,8 @@ export function optionsBasisFootnote(basis: OptionsChainBasis): string {
|
|
|
180
193
|
const parts: string[] = [];
|
|
181
194
|
if (basis.spot) {
|
|
182
195
|
parts.push(`Greeks at spot ${formatModelSpotPrice(basis.spot.price)} (${modelSpotLabel(basis.spot)}), `
|
|
183
|
-
|
|
196
|
+
// An unknown yield is valued as none, and says so rather than read as a stated 0%.
|
|
197
|
+
+ `dividend yield ${basis.dividendYield == null ? "unknown (valued at 0%)" : `${Number((basis.dividendYield * 100).toFixed(2))}%`}, `
|
|
184
198
|
+ `rate ${Number((OPTION_MODEL_RATE * 100).toFixed(2))}%`);
|
|
185
199
|
}
|
|
186
200
|
const oi = basis.openInterest;
|
|
@@ -7,10 +7,18 @@ import {
|
|
|
7
7
|
fetchScreenerResult,
|
|
8
8
|
fetchTrending,
|
|
9
9
|
MARKET_SUMMARY_SYMBOLS,
|
|
10
|
-
rankScreenerQuotes,
|
|
11
10
|
type ScreenerCategory,
|
|
12
11
|
} from "../../plugins/builtin/market-movers/screener";
|
|
13
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
formatMoverPrice,
|
|
14
|
+
MOVER_RANK_BASES,
|
|
15
|
+
MOVER_RANKING_LABELS,
|
|
16
|
+
moverReferencePrice,
|
|
17
|
+
moverRegularSession,
|
|
18
|
+
rankMoverQuotes,
|
|
19
|
+
type MoverRankBasis,
|
|
20
|
+
} from "../../plugins/builtin/market-movers/model";
|
|
21
|
+
import { MOVER_CANDIDATES } from "../../plugins/builtin/market-movers/client";
|
|
14
22
|
import { EXTENDED_SESSION_LABELS, getQuoteSessionFields, type ExtendedSession } from "../../market-data/market/status";
|
|
15
23
|
import {
|
|
16
24
|
COUNTRY_CYCLE,
|
|
@@ -111,7 +119,16 @@ const MOVER_LISTS: Record<string, ScreenerCategory | "trending"> = {
|
|
|
111
119
|
"most-active": "most_actives",
|
|
112
120
|
trending: "trending",
|
|
113
121
|
};
|
|
114
|
-
const MOVERS_USAGE = "movers [gainers|losers|active|trending]";
|
|
122
|
+
const MOVERS_USAGE = "movers [gainers|losers|active|trending] [--session regular|extended]";
|
|
123
|
+
/** The most rows the movers list serves in one request. */
|
|
124
|
+
const MOVER_REQUEST_CAP = 100;
|
|
125
|
+
|
|
126
|
+
function moverRankBasis(value: string | undefined, ctx: Parameters<CliCommandDef["execute"]>[1]): MoverRankBasis {
|
|
127
|
+
if (value == null) return "regular";
|
|
128
|
+
const basis = MOVER_RANK_BASES.find((candidate) => candidate === value.trim().toLowerCase());
|
|
129
|
+
if (!basis) ctx.fail(`Unknown --session "${value}".`, "Use regular or extended.");
|
|
130
|
+
return basis;
|
|
131
|
+
}
|
|
115
132
|
|
|
116
133
|
function screenerCategory(args: readonly string[], ctx: Parameters<CliCommandDef["execute"]>[1]): ScreenerCategory | "trending" {
|
|
117
134
|
rejectExtraArgs(args, 1, { usage: MOVERS_USAGE, takes: "one list" }, ctx);
|
|
@@ -154,9 +171,16 @@ function quoteRows(results: Awaited<ReturnType<NonNullable<import("../../types/d
|
|
|
154
171
|
});
|
|
155
172
|
}
|
|
156
173
|
|
|
157
|
-
async function runMoverCommand(
|
|
174
|
+
async function runMoverCommand(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
175
|
+
const args = [...rawArgs];
|
|
176
|
+
const sessionOption = takeOption(args, "--session");
|
|
177
|
+
const basis = moverRankBasis(sessionOption, ctx);
|
|
158
178
|
const category = screenerCategory(args, ctx);
|
|
159
179
|
const limit = ctx.cliOptions.limit ?? 25;
|
|
180
|
+
if (sessionOption != null && (category === "trending" || category === "most_actives")) {
|
|
181
|
+
ctx.fail("--session ranks Gainers and Losers.", category === "trending"
|
|
182
|
+
? "Trending keeps its source's order." : "Most active ranks by volume.");
|
|
183
|
+
}
|
|
160
184
|
if (category === "trending") {
|
|
161
185
|
await withCliServices(ctx, async (services) => {
|
|
162
186
|
const trending = await fetchTrending(limit, undefined, { forceRefresh: ctx.cliOptions.refresh });
|
|
@@ -171,11 +195,17 @@ async function runMoverCommand(args: string[], ctx: Parameters<CliCommandDef["ex
|
|
|
171
195
|
return;
|
|
172
196
|
}
|
|
173
197
|
|
|
174
|
-
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
198
|
+
// After the close a price list keeps only the rows moving its way on the basis
|
|
199
|
+
// asked for, so it reads more candidates than it shows.
|
|
200
|
+
const count = category === "most_actives" ? limit : Math.min(MOVER_REQUEST_CAP, Math.max(limit, MOVER_CANDIDATES));
|
|
201
|
+
const screener = await fetchScreenerResult(category, count, undefined, { forceRefresh: ctx.cliOptions.refresh, session: basis });
|
|
202
|
+
// Last and Chg% are the regular session, as `ticker` reads it, and a
|
|
203
|
+
// pre-market or after-hours print is its own column. The list is ranked by
|
|
204
|
+
// the regular session's move (its close's after the close), or with
|
|
205
|
+
// --session extended by the extended print's move from that close.
|
|
206
|
+
const { quotes, rankedBy } = rankMoverQuotes(category, screener.data.map(moverRegularSession), basis);
|
|
207
|
+
const rows = quotes.slice(0, limit).map((quote) => {
|
|
208
|
+
const { extended, ...row } = quote;
|
|
179
209
|
return {
|
|
180
210
|
...row,
|
|
181
211
|
extendedSession: extended?.session ?? null,
|
|
@@ -184,8 +214,15 @@ async function runMoverCommand(args: string[], ctx: Parameters<CliCommandDef["ex
|
|
|
184
214
|
extendedChangePercent: extended?.changePercent ?? null,
|
|
185
215
|
};
|
|
186
216
|
});
|
|
217
|
+
const fellBack = basis === "extended" && rankedBy === "regular";
|
|
187
218
|
// A screener snapshot, as the MOST report reads it: delayed, each row dated by its own last price.
|
|
188
|
-
ctx.printResult({
|
|
219
|
+
ctx.printResult({
|
|
220
|
+
data: rows,
|
|
221
|
+
metadata: { category, rankedBy },
|
|
222
|
+
...(fellBack ? { warnings: ["No pre-market or after-hours prints now, so the list is ranked by the regular-session move."] } : {}),
|
|
223
|
+
freshness: rowsFreshness(rows, { status: "delayed" }, { stale: screener.stale === true }),
|
|
224
|
+
}, {
|
|
225
|
+
heading: `Ranked by ${MOVER_RANKING_LABELS[rankedBy]}`,
|
|
189
226
|
columns: [
|
|
190
227
|
...MOVER_COLUMNS.slice(0, 4),
|
|
191
228
|
...EXTENDED_COLUMNS,
|
|
@@ -428,7 +465,7 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
428
465
|
}),
|
|
429
466
|
}, {
|
|
430
467
|
dateKey: "date",
|
|
431
|
-
empty: fredEmptyMessage(startDate, latest),
|
|
468
|
+
empty: fredEmptyMessage(startDate, latest == null ? null : exportRow(latest)),
|
|
432
469
|
...(notes.length ? { footnote: notes.join("\n"), exportNotes: notes } : {}),
|
|
433
470
|
exportIdentity: { symbolOrSeries: seriesId, currencyOrUnit: unit, interval },
|
|
434
471
|
textColumns: [
|
|
@@ -486,7 +523,7 @@ async function runYieldCurve(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
486
523
|
key: "changeBasisPoints",
|
|
487
524
|
header: "1D bp",
|
|
488
525
|
align: "right",
|
|
489
|
-
format: (value) => typeof value === "number" ? formatBasisPoints(value / 100) : "-",
|
|
526
|
+
format: (value) => typeof value === "number" ? formatBasisPoints(value / 100, 1) : "-",
|
|
490
527
|
},
|
|
491
528
|
{ key: "date", header: "Date" },
|
|
492
529
|
{ key: "seriesId", header: "Series" },
|
|
@@ -557,7 +594,11 @@ export const overviewCliCommands: CliCommandDef[] = [
|
|
|
557
594
|
help: {
|
|
558
595
|
group: CLI_COMMAND_GROUPS.markets,
|
|
559
596
|
usage: [MOVERS_USAGE],
|
|
560
|
-
|
|
597
|
+
options: [{
|
|
598
|
+
flags: "--session <basis>",
|
|
599
|
+
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)",
|
|
600
|
+
}],
|
|
601
|
+
examples: ["movers", "movers losers --limit 10", "movers gainers --session extended", "movers trending"],
|
|
561
602
|
},
|
|
562
603
|
execute: runMoverCommand,
|
|
563
604
|
},
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
type TickerSearchCandidate,
|
|
17
17
|
} from "../../tickers/search";
|
|
18
18
|
import { initMarketData, withMarketData } from "../context";
|
|
19
|
-
import { loadListingFate } from "
|
|
19
|
+
import { loadListingFate } from "../../tickers/listing-fate";
|
|
20
20
|
import { formatMarketPriceWithCurrency, quoteFormatOptions } from "../../market-data/market/format";
|
|
21
21
|
import { startTickerChangeLookup, tickerChangeLine, type TickerChangeLookup } from "../../tickers/ticker-changes";
|
|
22
22
|
import { fail } from "../errors";
|
|
@@ -4,6 +4,7 @@ import { saveConfig } from "../../data/config/store";
|
|
|
4
4
|
import { builtinPluginGroupMembers } from "../../plugins/ownership";
|
|
5
5
|
import type { AppConfig, TelemetryConfig } from "../../types/config";
|
|
6
6
|
import { canonicalTimeZone } from "../../utils/utc-time";
|
|
7
|
+
import { NIGHTLY_CHANNEL_SUMMARY, parseUpdateChannel, withUpdateChannel } from "../../updater/channel";
|
|
7
8
|
import type { CliCommandDef } from "../../types/plugin";
|
|
8
9
|
import { resolveCliDataDir, withCliServices, withConfigData } from "../context";
|
|
9
10
|
import { CLI_COMMAND_GROUPS } from "../help";
|
|
@@ -33,6 +34,8 @@ const STAR_PROMPT_CONFIG_KEY = "starPrompt.enabled";
|
|
|
33
34
|
const TIMEZONE_CONFIG_KEY = "timezone";
|
|
34
35
|
/** What `config set timezone` takes to go back to UTC alone. */
|
|
35
36
|
const NO_TIMEZONE = "none";
|
|
37
|
+
/** Stable releases, or nightly builds as well. */
|
|
38
|
+
const UPDATE_CHANNEL_CONFIG_KEY = "updates.channel";
|
|
36
39
|
const EDITABLE_CONFIG_KEYS = [
|
|
37
40
|
"baseCurrency",
|
|
38
41
|
"refreshIntervalMinutes",
|
|
@@ -41,6 +44,7 @@ const EDITABLE_CONFIG_KEYS = [
|
|
|
41
44
|
...Object.keys(TELEMETRY_CONFIG_KEYS),
|
|
42
45
|
STAR_PROMPT_CONFIG_KEY,
|
|
43
46
|
TIMEZONE_CONFIG_KEY,
|
|
47
|
+
UPDATE_CHANNEL_CONFIG_KEY,
|
|
44
48
|
];
|
|
45
49
|
|
|
46
50
|
function isTelemetryConfigKey(key: string): key is TelemetryConfigKey {
|
|
@@ -219,6 +223,7 @@ export function createSystemCliCommands(): CliCommandDef[] {
|
|
|
219
223
|
"config set telemetry.usage false",
|
|
220
224
|
"config set starPrompt.enabled false",
|
|
221
225
|
"config set timezone <Region/City>|none",
|
|
226
|
+
"config set updates.channel stable|nightly",
|
|
222
227
|
"config get keybindings",
|
|
223
228
|
"config set keybindings.actions.<action> <keys>|null|default",
|
|
224
229
|
"config set keybindings.commands.<keys> <command>|null",
|
|
@@ -236,6 +241,11 @@ export function createSystemCliCommands(): CliCommandDef[] {
|
|
|
236
241
|
lines: [
|
|
237
242
|
`theme takes one of these ids, or its name as config themes lists it: ${getThemeIds().join(", ")}.`,
|
|
238
243
|
],
|
|
244
|
+
}, {
|
|
245
|
+
title: "Release channel",
|
|
246
|
+
lines: [
|
|
247
|
+
`The app updates itself from stable releases. With updates.channel nightly (Release Channel in the command bar) it also installs the nightly build: ${NIGHTLY_CHANNEL_SUMMARY}. Back on stable, a nightly build stays until a newer stable release is out. GLOOMBERB_UPDATE_CHANNEL overrides the setting for one run; npm and Bun installs always follow stable.`,
|
|
248
|
+
],
|
|
239
249
|
}],
|
|
240
250
|
examples: [
|
|
241
251
|
"config",
|
|
@@ -287,6 +297,7 @@ export function createSystemCliCommands(): CliCommandDef[] {
|
|
|
287
297
|
"telemetry.attention": context.config.telemetry?.attention === true,
|
|
288
298
|
[STAR_PROMPT_CONFIG_KEY]: context.config.starPrompt?.enabled !== false,
|
|
289
299
|
[TIMEZONE_CONFIG_KEY]: context.config.timezone ?? null,
|
|
300
|
+
[UPDATE_CHANNEL_CONFIG_KEY]: context.config.updates?.channel ?? "stable",
|
|
290
301
|
};
|
|
291
302
|
|
|
292
303
|
if (action === "list") {
|
|
@@ -347,11 +358,16 @@ export function createSystemCliCommands(): CliCommandDef[] {
|
|
|
347
358
|
if ((isTelemetryConfigKey(key) || key === STAR_PROMPT_CONFIG_KEY) && value !== "true" && value !== "false") {
|
|
348
359
|
ctx.fail(`Usage: gloomberb config set ${key} true|false`);
|
|
349
360
|
}
|
|
361
|
+
if (key === UPDATE_CHANNEL_CONFIG_KEY && !parseUpdateChannel(value)) {
|
|
362
|
+
ctx.fail(`Usage: gloomberb config set ${key} stable|nightly`);
|
|
363
|
+
}
|
|
350
364
|
// A display name may come unquoted: config set theme White Phosphor.
|
|
351
365
|
const parsedValue = key === "theme"
|
|
352
366
|
? requireThemeId(args.slice(2).join(" "))
|
|
353
367
|
: key === TIMEZONE_CONFIG_KEY
|
|
354
368
|
? parseTimezoneSetting(value)
|
|
369
|
+
: key === UPDATE_CHANNEL_CONFIG_KEY
|
|
370
|
+
? parseUpdateChannel(value)
|
|
355
371
|
: key === "refreshIntervalMinutes"
|
|
356
372
|
? Number(value)
|
|
357
373
|
: isBooleanConfigKey(key)
|
|
@@ -363,7 +379,9 @@ export function createSystemCliCommands(): CliCommandDef[] {
|
|
|
363
379
|
? { ...context.config, starPrompt: { ...context.config.starPrompt, enabled: parsedValue as boolean } }
|
|
364
380
|
: key === TIMEZONE_CONFIG_KEY
|
|
365
381
|
? withTimezone(context.config, parsedValue as string | null)
|
|
366
|
-
:
|
|
382
|
+
: key === UPDATE_CHANNEL_CONFIG_KEY
|
|
383
|
+
? withUpdateChannel(context.config, parsedValue as "stable" | "nightly")
|
|
384
|
+
: { ...context.config, [key]: parsedValue };
|
|
367
385
|
if (!ctx.cliOptions.dryRun) await saveConfig(nextConfig);
|
|
368
386
|
ctx.printResult({
|
|
369
387
|
data: {
|