gloomberb 0.18.4 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +169 -5
- package/src/account/auth-model.ts +7 -3
- package/src/api-client/auth.ts +9 -4
- package/src/api-client/catalysts.ts +2 -1
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +31 -6
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/index.ts +171 -11
- package/src/api-client/live-scan.ts +91 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +13 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/request.ts +222 -41
- package/src/api-client/research-activity.ts +6 -12
- package/src/api-client/types.ts +74 -0
- package/src/app/keybindings/index.ts +3 -0
- package/src/app/keybindings/labels.ts +18 -1
- package/src/app/keybindings/resolve.ts +14 -0
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +42 -27
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +1 -1
- package/src/architecture/pane-tabs-allowlist.json +3 -0
- package/src/architecture/ttl-caches-allowlist.json +19 -0
- package/src/brokers/sync-broker-instance.ts +2 -8
- package/src/capabilities/chart-series.ts +1 -1
- package/src/cli/commands/crypto-hints.ts +40 -24
- package/src/cli/commands/fred-series.ts +5 -0
- package/src/cli/commands/keybindings.ts +23 -0
- package/src/cli/commands/market.ts +138 -46
- package/src/cli/commands/options-chain.ts +107 -10
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +176 -39
- package/src/cli/commands/search.ts +39 -3
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +57 -35
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +10 -2
- package/src/cli/export-source.ts +29 -1
- package/src/cli/glossary.ts +73 -1
- package/src/cli/history-rows.ts +87 -16
- package/src/cli/index.ts +2 -0
- package/src/cli/known-gaps.ts +5 -2
- package/src/cli/listing-arg.ts +195 -56
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +3 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +111 -16
- package/src/cli/pane-functions/function-help.ts +23 -13
- package/src/cli/pane-functions/headless.ts +56 -12
- package/src/cli/pane-functions/index.ts +58 -16
- package/src/cli/pane-functions/market-session.ts +81 -9
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/report.ts +5 -1
- package/src/cli/pane-functions/resolver.ts +35 -8
- package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
- package/src/cli/pane-functions/screenshot-types.ts +5 -0
- package/src/cli/quote-answer.ts +30 -13
- package/src/cli/report-tables.ts +18 -7
- package/src/cli/restore-plugins.ts +13 -1
- package/src/cli/result.ts +57 -5
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +26 -4
- package/src/components/chart/composite/format.ts +27 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/levels.ts +2 -0
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +61 -5
- package/src/components/chart/composite/range-statistics.ts +62 -0
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +46 -7
- package/src/components/chart/composite/series-axis-tags.tsx +16 -9
- package/src/components/chart/composite/series-cues.ts +14 -0
- package/src/components/chart/composite/text-renderer.ts +9 -2
- package/src/components/chart/composite/tool-catalog.ts +36 -0
- package/src/components/chart/composite/tools.ts +203 -44
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
- package/src/components/command-bar/assist/inventory.ts +2 -1
- package/src/components/command-bar/commands/direct/index.ts +28 -13
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/panel/prompt-binding.ts +0 -8
- package/src/components/command-bar/workflow/alert.ts +1 -1
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/header.tsx +26 -40
- package/src/components/layout/pane/footer/index.tsx +1 -0
- package/src/components/layout/pane/footer/model.ts +27 -0
- package/src/components/onboarding/onboarding-frame.tsx +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
- package/src/components/onboarding/wizard-broker-sync.ts +0 -1
- package/src/components/onboarding/wizard-positions.ts +3 -3
- package/src/components/ticker/list-table-view.tsx +4 -1
- package/src/components/ui/choice-dialog.tsx +23 -18
- package/src/components/ui/choice-groups.ts +18 -0
- package/src/components/ui/data-table/opentui/index.tsx +9 -7
- package/src/components/ui/data-table/opentui/model.ts +26 -1
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/display.tsx +5 -1
- package/src/components/ui/list-view.tsx +8 -4
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +1 -0
- package/src/components/ui/select-button.tsx +5 -1
- package/src/components/ui/select-field.tsx +1 -0
- package/src/components/ui/table-layout.ts +8 -5
- package/src/core/app-runtime.ts +2 -0
- package/src/core/app-services.ts +2 -2
- package/src/core/state/app/layout.ts +2 -0
- package/src/core/state/app/state.ts +9 -0
- package/src/core/state/app/types.ts +3 -0
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/saved-copies.ts +55 -16
- package/src/data/ticker-repository.ts +16 -9
- package/src/data/ticker-writes.ts +81 -0
- package/src/data/ttl-cache.ts +165 -0
- package/src/docs/snapshot.json +427 -216
- package/src/i18n/es.ts +19 -3
- package/src/i18n/ja.ts +19 -3
- package/src/i18n/ko.ts +19 -3
- package/src/i18n/zh-cn.ts +19 -3
- package/src/i18n/zh-tw.ts +19 -3
- package/src/market-data/coordinator/financials.ts +10 -2
- package/src/market-data/coordinator/index.ts +3 -1
- package/src/market-data/coordinator/quotes.ts +4 -2
- package/src/market-data/depositary-pair.ts +2 -2
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +48 -7
- package/src/market-data/market/dated-previous-close.ts +203 -0
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/market/futures-week.ts +68 -0
- package/src/market-data/market/status.ts +16 -4
- package/src/market-data/options-alternatives.ts +141 -4
- package/src/market-data/quotes/contributions.ts +20 -2
- package/src/market-data/quotes/resolution.ts +3 -0
- package/src/market-data/selectors.ts +3 -0
- package/src/news/aggregator.ts +113 -9
- package/src/news/cloud-session.ts +21 -0
- package/src/news/hooks.ts +1 -0
- package/src/news/news-model.ts +1 -0
- package/src/news/types.ts +11 -0
- package/src/notifications/notification-log.ts +6 -4
- package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +380 -82
- package/src/plugins/builtin/alerts/command.ts +31 -0
- package/src/plugins/builtin/alerts/constants.ts +1 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- package/src/plugins/builtin/alerts/fired-log.ts +113 -8
- package/src/plugins/builtin/alerts/format.ts +80 -4
- package/src/plugins/builtin/alerts/headless.ts +30 -7
- package/src/plugins/builtin/alerts/history-rows.ts +7 -3
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +135 -22
- package/src/plugins/builtin/alerts/live.ts +3 -3
- package/src/plugins/builtin/alerts/metric-command.ts +134 -0
- package/src/plugins/builtin/alerts/metric-data.ts +111 -0
- package/src/plugins/builtin/alerts/metric-model.ts +324 -0
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
- package/src/plugins/builtin/alerts/pane.tsx +90 -39
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +37 -10
- package/src/plugins/builtin/alerts/types.ts +7 -0
- package/src/plugins/builtin/analytics/index.tsx +2 -1
- package/src/plugins/builtin/analytics/risk-client.ts +16 -5
- package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
- package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
- package/src/plugins/builtin/analytics/risk-model.ts +55 -9
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/attention/index.tsx +2 -1
- package/src/plugins/builtin/attention/listing.ts +14 -0
- package/src/plugins/builtin/backtest/headless.ts +10 -5
- package/src/plugins/builtin/backtest/pane.tsx +9 -4
- package/src/plugins/builtin/backtest/presets.ts +29 -3
- package/src/plugins/builtin/broker-manager/index.tsx +10 -5
- package/src/plugins/builtin/broker-manager/model.ts +6 -1
- package/src/plugins/builtin/broker-trading/controller.ts +22 -12
- package/src/plugins/builtin/broker-trading/index.tsx +13 -1
- package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
- package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +12 -4
- package/src/plugins/builtin/buildout/format.ts +38 -0
- package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
- package/src/plugins/builtin/buildout/table-model.ts +23 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +7 -3
- package/src/plugins/builtin/catalysts/model.ts +2 -1
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
- package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
- package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
- package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
- package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
- package/src/plugins/builtin/chart-composer/formula.ts +71 -18
- package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
- package/src/plugins/builtin/chart-composer/headless.ts +19 -3
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
- package/src/plugins/builtin/chart-composer/presets.ts +50 -15
- package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
- package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
- package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
- package/src/plugins/builtin/chat/content/composer.tsx +13 -6
- package/src/plugins/builtin/chat/content/footer.tsx +6 -3
- package/src/plugins/builtin/chat/content/index.tsx +50 -21
- package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
- package/src/plugins/builtin/chat/index.tsx +23 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
- package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
- package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
- package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
- package/src/plugins/builtin/clinical-trials/model.ts +0 -9
- package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +1 -1
- package/src/plugins/builtin/company-attention/building.tsx +5 -1
- package/src/plugins/builtin/company-attention/client.ts +8 -9
- package/src/plugins/builtin/company-attention/evidence.ts +4 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +9 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/correlation/headless.ts +13 -5
- package/src/plugins/builtin/correlation/history.ts +22 -0
- package/src/plugins/builtin/cot/client.ts +22 -18
- package/src/plugins/builtin/cot/headless.ts +15 -10
- package/src/plugins/builtin/cot/model.ts +14 -1
- package/src/plugins/builtin/cot/pane.tsx +22 -14
- package/src/plugins/builtin/credit-boards/client.ts +6 -10
- package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
- package/src/plugins/builtin/credit-conditions/model.ts +4 -0
- package/src/plugins/builtin/credit-conditions/series.ts +3 -1
- package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
- package/src/plugins/builtin/credit-documents/export.ts +173 -0
- package/src/plugins/builtin/credit-documents/headless.ts +7 -7
- package/src/plugins/builtin/credit-documents/model.ts +104 -14
- package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
- package/src/plugins/builtin/custom-view/sources.ts +2 -0
- package/src/plugins/builtin/dividend-yield/client.ts +131 -23
- package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
- package/src/plugins/builtin/dividend-yield/model.ts +8 -5
- package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
- package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
- package/src/plugins/builtin/dividend-yield/types.ts +20 -2
- package/src/plugins/builtin/dividend-yield/view.ts +30 -2
- package/src/plugins/builtin/docs/targets.ts +5 -1
- package/src/plugins/builtin/earnings/board-model.ts +16 -15
- package/src/plugins/builtin/earnings/board.tsx +62 -71
- package/src/plugins/builtin/earnings/headless.ts +40 -4
- package/src/plugins/builtin/earnings/scope.ts +31 -43
- package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
- package/src/plugins/builtin/econ/calendar-model.ts +14 -9
- package/src/plugins/builtin/econ/figures.ts +72 -0
- package/src/plugins/builtin/econ/index.tsx +33 -3
- package/src/plugins/builtin/econ/types.ts +4 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
- package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
- package/src/plugins/builtin/econ-statistics/view.ts +3 -0
- package/src/plugins/builtin/equity-screener/categories.ts +179 -0
- package/src/plugins/builtin/equity-screener/client.ts +4 -2
- package/src/plugins/builtin/equity-screener/headless.ts +144 -13
- package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
- package/src/plugins/builtin/equity-screener/model.ts +99 -10
- package/src/plugins/builtin/equity-screener/pane.tsx +70 -15
- package/src/plugins/builtin/exposure/charts.tsx +2 -2
- package/src/plugins/builtin/exposure/model.ts +13 -1
- package/src/plugins/builtin/exposure/pane.tsx +4 -4
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/futures/model.ts +2 -1
- package/src/plugins/builtin/futures/table.tsx +2 -2
- package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
- package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
- package/src/plugins/builtin/fx-matrix/client.ts +11 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
- package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
- package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -3
- package/src/plugins/builtin/gpu/headless.ts +8 -2
- package/src/plugins/builtin/help/function-index.ts +4 -1
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +18 -1
- package/src/plugins/builtin/iv-history/client.ts +52 -11
- package/src/plugins/builtin/iv-history/format.ts +36 -23
- package/src/plugins/builtin/iv-history/headless.ts +36 -18
- package/src/plugins/builtin/iv-history/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/model.ts +163 -27
- package/src/plugins/builtin/iv-history/pane.tsx +10 -6
- package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
- package/src/plugins/builtin/iv-history/universe.ts +5 -1
- package/src/plugins/builtin/jobs/client.ts +25 -29
- package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
- package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
- package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
- package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
- package/src/plugins/builtin/macro-day/pane.tsx +7 -5
- package/src/plugins/builtin/macro-day/release-table.ts +2 -2
- package/src/plugins/builtin/market-heatmap/data.ts +8 -33
- package/src/plugins/builtin/market-movers/client.ts +25 -7
- package/src/plugins/builtin/market-movers/footer.ts +2 -2
- package/src/plugins/builtin/market-movers/headless.ts +32 -12
- package/src/plugins/builtin/market-movers/index.tsx +26 -14
- package/src/plugins/builtin/market-movers/min-float.ts +74 -0
- package/src/plugins/builtin/market-movers/model.ts +4 -1
- package/src/plugins/builtin/market-movers/screener.ts +14 -0
- package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
- package/src/plugins/builtin/market-movers/session-table.ts +4 -3
- package/src/plugins/builtin/market-movers/table.ts +17 -5
- package/src/plugins/builtin/members/breadth-pane.tsx +82 -0
- package/src/plugins/builtin/members/client.ts +19 -4
- package/src/plugins/builtin/members/headless.ts +20 -7
- package/src/plugins/builtin/members/index.tsx +9 -9
- package/src/plugins/builtin/members/model.ts +23 -4
- package/src/plugins/builtin/members/pane.tsx +25 -15
- package/src/plugins/builtin/news/index.tsx +1 -0
- package/src/plugins/builtin/news/wire/categories.ts +19 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
- package/src/plugins/builtin/openfda/client.ts +14 -2
- package/src/plugins/builtin/openfda/index.tsx +3 -2
- package/src/plugins/builtin/openfda/model.ts +9 -0
- package/src/plugins/builtin/openfda/pane.tsx +42 -6
- package/src/plugins/builtin/options/analytics.ts +54 -6
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/model-spot.ts +5 -2
- package/src/plugins/builtin/options/strike-window.ts +2 -1
- package/src/plugins/builtin/options/table.ts +6 -9
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +42 -12
- package/src/plugins/builtin/options-positioning/client.ts +30 -0
- package/src/plugins/builtin/options-positioning/headless.ts +74 -21
- package/src/plugins/builtin/options-positioning/index.tsx +4 -4
- package/src/plugins/builtin/options-positioning/model.ts +122 -14
- package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
- package/src/plugins/builtin/options-scenario/client.ts +116 -47
- package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
- package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
- package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
- package/src/plugins/builtin/options-scenario/csp.ts +8 -2
- package/src/plugins/builtin/options-scenario/index.tsx +9 -7
- package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
- package/src/plugins/builtin/pe-band/model.ts +12 -49
- package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
- package/src/plugins/builtin/perps/cascade.ts +236 -0
- package/src/plugins/builtin/perps/client.ts +56 -2
- package/src/plugins/builtin/perps/freshness.ts +12 -10
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/live-rows.ts +10 -3
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +32 -31
- package/src/plugins/builtin/perps/status-line.ts +25 -0
- package/src/plugins/builtin/plugin-module.ts +5 -1
- package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +12 -7
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/rate-path/pane.tsx +2 -2
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/client.ts +18 -4
- package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
- package/src/plugins/builtin/relative-rotation/model.ts +16 -40
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
- package/src/plugins/builtin/research/event-model.ts +10 -8
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
- package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
- package/src/plugins/builtin/scanner/feed.ts +2 -2
- package/src/plugins/builtin/scanner/flow-model.ts +50 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
- package/src/plugins/builtin/scanner/flow-session.ts +146 -0
- package/src/plugins/builtin/scanner/headless.ts +204 -35
- package/src/plugins/builtin/scanner/index.ts +40 -6
- package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
- package/src/plugins/builtin/scanner/live-feed.ts +168 -0
- package/src/plugins/builtin/scanner/live-headless.ts +138 -0
- package/src/plugins/builtin/scanner/live-model.ts +362 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
- package/src/plugins/builtin/scanner/live-track.ts +186 -0
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/headless.ts +10 -1
- package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
- package/src/plugins/builtin/sec/fund-facts.ts +116 -34
- package/src/plugins/builtin/sec/headless.ts +2 -1
- package/src/plugins/builtin/series-inventory.ts +3 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
- package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
- package/src/plugins/builtin/shared/earnings-report.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/macro-releases.ts +65 -0
- package/src/plugins/builtin/shared/members-menu.tsx +3 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
- package/src/plugins/builtin/shared/report-freshness.ts +3 -2
- package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
- package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
- package/src/plugins/builtin/shared/ticker-company.ts +77 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +10 -3
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/headless.ts +6 -4
- package/src/plugins/builtin/themes/model.ts +17 -8
- package/src/plugins/builtin/themes/pane.tsx +7 -7
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
- package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
- package/src/plugins/builtin/time-sales/pane.tsx +31 -19
- package/src/plugins/builtin/treasury-daily/client.ts +41 -5
- package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
- package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
- package/src/plugins/builtin/treasury-daily/model.ts +50 -3
- package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
- package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
- package/src/plugins/builtin/vol-surface/client.ts +9 -17
- package/src/plugins/builtin/vol-surface/headless.ts +63 -14
- package/src/plugins/builtin/vol-surface/index.tsx +2 -2
- package/src/plugins/builtin/vol-surface/model.ts +22 -7
- package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
- package/src/plugins/builtin/vol-surface/tables.ts +31 -3
- package/src/plugins/builtin/volatility/dvol.ts +46 -0
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +4 -4
- package/src/plugins/builtin/volatility/series.ts +26 -0
- package/src/plugins/builtin/world-indices/footer.ts +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-browser.ts +13 -8
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +5 -5
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
- package/src/remote/resources.ts +2 -1
- package/src/renderers/browser/app-services.ts +1 -7
- package/src/renderers/browser/config-host.ts +2 -55
- package/src/renderers/browser/main.tsx +7 -57
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/list-view.tsx +13 -10
- package/src/renderers/dom/desktop/query-bar.tsx +4 -2
- package/src/renderers/dom/desktop/select-field.tsx +4 -2
- package/src/renderers/dom/host/chart-surface.tsx +21 -0
- package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
- package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
- package/src/renderers/electrobun/bun/index.ts +25 -1
- package/src/renderers/electrobun/shared/app-name.ts +30 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +3 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/renderers/opentui/toast-host.tsx +71 -43
- package/src/series/registry.ts +1 -0
- package/src/series/types.ts +6 -0
- package/src/series/validate.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +1 -0
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
- package/src/sources/provider-router/batches.ts +10 -2
- package/src/sources/provider-router/financial-routes.ts +3 -3
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/history.ts +28 -7
- package/src/sources/provider-router/index.ts +149 -10
- package/src/sources/provider-router/search.ts +13 -2
- package/src/state/app/context/index.tsx +3 -0
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/auth-transition.ts +19 -9
- package/src/sync/baseline.ts +10 -4
- package/src/sync/controller.ts +70 -19
- package/src/sync/core-contributors.ts +90 -31
- package/src/sync/react.ts +4 -1
- package/src/sync/types.ts +3 -0
- package/src/telemetry/stall-reports-dom.ts +38 -0
- package/src/telemetry/stall-reports-node.ts +75 -0
- package/src/telemetry/stall-reports.ts +214 -0
- package/src/telemetry/usage-counts.ts +0 -18
- package/src/test-support/preload.ts +14 -0
- package/src/theme/colors.ts +8 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name.ts +103 -3
- package/src/tickers/listing-venues.ts +38 -3
- package/src/tickers/search/bare-symbol.ts +36 -0
- package/src/tickers/search/index.ts +36 -7
- package/src/tickers/search/ranking.ts +10 -1
- package/src/tickers/search/types.ts +5 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/base-series.ts +13 -5
- package/src/time-series/daily-valuation.ts +62 -0
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +2 -4
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/forward-valuation.ts +32 -0
- package/src/time-series/fundamentals.ts +198 -7
- package/src/time-series/hooks.ts +1 -0
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/price-units.ts +72 -2
- package/src/time-series/resolve-context.ts +7 -1
- package/src/time-series/resolve.ts +116 -15
- package/src/time-series/series-presentation.ts +40 -6
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +5 -3
- package/src/time-series/studies.ts +278 -74
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +3 -3
- package/src/time-series/types.ts +27 -1
- package/src/time-series/valuation-releases.ts +39 -0
- package/src/types/data-provider.ts +9 -1
- package/src/types/desktop-window.ts +2 -0
- package/src/types/financials.ts +73 -2
- package/src/types/headless.ts +61 -5
- package/src/types/instrument.ts +3 -0
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +10 -1
- package/src/ui/host.tsx +3 -0
- package/src/updater/command-names.ts +12 -7
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +44 -0
- package/src/utils/financial-statements.ts +6 -1
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +24 -3
- package/src/utils/fx-market-hours.ts +26 -3
- package/src/utils/main-thread-stall.ts +35 -2
- package/src/utils/market-capitalization.ts +24 -1
- package/src/utils/network-failure.ts +9 -6
- package/src/utils/option-expiry.ts +48 -1
- package/src/utils/options.ts +10 -0
- package/src/utils/plugin-proxy-hosts.json +1 -0
- package/src/utils/price-history.ts +12 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
- package/src/renderers/browser/first-screen.ts +0 -193
|
@@ -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,
|
|
@@ -13,11 +13,33 @@ import {
|
|
|
13
13
|
chainOpenInterestAsOf, loadListedOpenInterest, openInterestAsOfLabel, type OpenInterestAsOf, statedOpenInterestAsOf,
|
|
14
14
|
} from "../../plugins/builtin/options/open-interest-as-of";
|
|
15
15
|
import { DEFAULT_OPTION_CALC_DRAFT } from "../../plugins/builtin/options-calculator/model";
|
|
16
|
-
import { expiryIsoDate, OPTION_EXPIRATION_PLACEHOLDER, parseOptionExpiration } from "../../utils/option-expiry";
|
|
16
|
+
import { expiryIsoDate, OPTION_EXPIRATION_PLACEHOLDER, parseOptionExpiration, settlementStampTime } from "../../utils/option-expiry";
|
|
17
17
|
import { loadModelDividendYield } from "../../market-data/model-dividend-yield";
|
|
18
|
+
import { formatUtcTime } from "../../utils/utc-time";
|
|
19
|
+
import { takeOption } from "./command-utils";
|
|
18
20
|
|
|
19
21
|
export const OPTIONS_USAGE = `options <symbol> [--expiration <${OPTION_EXPIRATION_PLACEHOLDER}>]`;
|
|
20
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
|
+
|
|
21
43
|
export { parseOptionExpiration };
|
|
22
44
|
|
|
23
45
|
/** Whether the chain holds contracts of the requested expiry, matched by UTC calendar date as the chain stamps them. */
|
|
@@ -62,16 +84,39 @@ export interface OptionModelInputs {
|
|
|
62
84
|
interface OptionModelFigures {
|
|
63
85
|
/** Annualized volatility as a fraction, solved from the chain's quotes. */
|
|
64
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;
|
|
65
89
|
delta: number | null;
|
|
66
90
|
}
|
|
67
91
|
|
|
68
92
|
/** The risk-free rate every options valuation in the app assumes. */
|
|
69
93
|
export const OPTION_MODEL_RATE = DEFAULT_OPTION_CALC_DRAFT.rate;
|
|
70
94
|
|
|
95
|
+
/** The venue's own marks a chain carries (Deribit's), stated in its JSON metadata in place of OPTION_VOLATILITY_FIELDS. */
|
|
96
|
+
export const MARKED_OPTION_FIELDS = {
|
|
97
|
+
iv: "The venue's mark implied volatility (decimal), one per contract. Delta and the other greeks under mark are the venue's too: "
|
|
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.",
|
|
100
|
+
bid: "Prices in USD are the coin prices (coin) at the expiry's forward (mark.forward), the venue's own model price.",
|
|
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.",
|
|
102
|
+
} as const;
|
|
103
|
+
|
|
104
|
+
/** Whether a venue marks the chain's contracts itself (Deribit): its IV and greeks are then used as they are. */
|
|
105
|
+
export function isMarkedChain(chain: OptionsChain): boolean {
|
|
106
|
+
return [...chain.calls, ...chain.puts].some((contract) => contract.mark != null);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The coin a chain's premiums are quoted in (BTC), when a venue quotes them in one. */
|
|
110
|
+
export function chainCoinCurrency(chain: OptionsChain): string | null {
|
|
111
|
+
return [...chain.calls, ...chain.puts].find((contract) => contract.coin)?.coin?.currency ?? null;
|
|
112
|
+
}
|
|
113
|
+
|
|
71
114
|
/** What each contract's two volatilities are, stated in the chain's JSON metadata so neither is read as the other. */
|
|
72
115
|
export const OPTION_VOLATILITY_FIELDS = {
|
|
73
116
|
iv: "Annualized volatility (decimal) solved from the quote midpoint, one per strike from its out-of-the-money side, at the "
|
|
74
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.",
|
|
75
120
|
impliedVolatility: "The data provider's own reported implied volatility, passed through unchanged. Delta does not use it; "
|
|
76
121
|
+ "GEX falls back to it only at a strike with no two-sided quote on either side.",
|
|
77
122
|
} as const;
|
|
@@ -97,8 +142,9 @@ function optionModelFigures(chain: OptionsChain, inputs: OptionModelInputs): Map
|
|
|
97
142
|
for (const contract of contracts) {
|
|
98
143
|
const volatility = volatilities.byStrike.get(contract.strike);
|
|
99
144
|
const delta = calculateOptionGreeks(contract, side, inputs.spot, inputs.dividendYield, volatilities)?.delta;
|
|
145
|
+
const iv = volatility != null && Number.isFinite(volatility) && volatility > 0 ? volatility : null;
|
|
100
146
|
figures.set(contract, {
|
|
101
|
-
iv:
|
|
147
|
+
iv, ivFilled: iv != null && volatilities.filled.has(contract.strike),
|
|
102
148
|
delta: delta != null && Number.isFinite(delta) ? delta : null,
|
|
103
149
|
});
|
|
104
150
|
}
|
|
@@ -148,7 +194,8 @@ export async function loadOptionModelInputs(
|
|
|
148
194
|
/** The chain with each contract's modeled `iv` and `delta` added; every provider field is kept. */
|
|
149
195
|
export function chainWithModelFigures(chain: OptionsChain, inputs: OptionModelInputs): OptionsChain {
|
|
150
196
|
const figures = optionModelFigures(chain, inputs);
|
|
151
|
-
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 });
|
|
152
199
|
return { ...chain, calls: chain.calls.map(add), puts: chain.puts.map(add) };
|
|
153
200
|
}
|
|
154
201
|
|
|
@@ -161,6 +208,13 @@ export interface OptionsChainBasis {
|
|
|
161
208
|
spot: OptionModelSpot | null;
|
|
162
209
|
dividendYield: number | null;
|
|
163
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);
|
|
164
218
|
}
|
|
165
219
|
|
|
166
220
|
/** The chain with `iv` and `delta` valued at the spot `optionModelSpot` picks for it, and that spot. */
|
|
@@ -188,16 +242,31 @@ export async function loadOptionsChainOpenInterest(symbol: string, chain: Option
|
|
|
188
242
|
}
|
|
189
243
|
|
|
190
244
|
/**
|
|
191
|
-
* The line under the chain: `Greeks at spot 89.79 (close Mon 12 Oct 20:00 UTC), dividend yield 2.34%, rate 4% ·
|
|
192
|
-
*
|
|
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.
|
|
247
|
+
* An open interest whose date cannot be established says why. A chain its
|
|
248
|
+
* venue marks says whose marks they are: `Deribit marks: IV and greeks at
|
|
249
|
+
* each expiry's forward, rate 0%; $ is the BTC price at the forward · OI live, Thu 22 Oct 22:42 UTC`.
|
|
193
250
|
*/
|
|
194
|
-
export function optionsBasisFootnote(basis: OptionsChainBasis): string {
|
|
251
|
+
export function optionsBasisFootnote(basis: OptionsChainBasis, chain?: OptionsChain): string {
|
|
195
252
|
const parts: string[] = [];
|
|
253
|
+
if (chain && isMarkedChain(chain)) {
|
|
254
|
+
const coin = chainCoinCurrency(chain);
|
|
255
|
+
parts.push(`${chain.venue ?? "Venue"} marks: IV and greeks at each expiry's forward, rate ${Number(((chain.rate ?? 0) * 100).toFixed(2))}%`
|
|
256
|
+
+ (coin ? `; $ is the ${coin} price at the forward` : ""));
|
|
257
|
+
// Its expiries settle at a stated time (08:00 UTC), which the expiration stamps carry.
|
|
258
|
+
const settles = [...chain.calls, ...chain.puts].map((contract) => settlementStampTime(contract.expiration)).find(Boolean);
|
|
259
|
+
if (settles) parts.push(`expiries settle ${settles}`);
|
|
260
|
+
const asOf = chain.asOf ? Date.parse(chain.asOf) : Number.NaN;
|
|
261
|
+
parts.push(Number.isFinite(asOf) ? `OI live, ${formatUtcTime(new Date(asOf).toISOString())}` : openInterestAsOfLabel(basis.openInterest));
|
|
262
|
+
return parts.join(" · ");
|
|
263
|
+
}
|
|
196
264
|
if (basis.spot) {
|
|
197
265
|
parts.push(`Greeks at spot ${formatModelSpotPrice(basis.spot.price)} (${modelSpotLabel(basis.spot)}), `
|
|
198
266
|
// An unknown yield is valued as none, and says so rather than read as a stated 0%.
|
|
199
267
|
+ `dividend yield ${basis.dividendYield == null ? "unknown (valued at 0%)" : `${Number((basis.dividendYield * 100).toFixed(2))}%`}, `
|
|
200
268
|
+ `rate ${Number((OPTION_MODEL_RATE * 100).toFixed(2))}%`);
|
|
269
|
+
parts.push(ivSolvedNote(basis.ivFilled === true));
|
|
201
270
|
}
|
|
202
271
|
const oi = basis.openInterest;
|
|
203
272
|
parts.push(oi?.date ? openInterestAsOfLabel(oi)
|
|
@@ -234,20 +303,48 @@ export function optionRows(chain: OptionsChain) {
|
|
|
234
303
|
volume: contract.volume,
|
|
235
304
|
openInterest: contract.openInterest,
|
|
236
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",
|
|
237
308
|
delta: modeled.delta ?? null,
|
|
238
309
|
expiration: expiryIsoDate(contract.expiration),
|
|
310
|
+
// A venue's marks and coin prices (Deribit), beside the dollar ones.
|
|
311
|
+
...(contract.mark ? {
|
|
312
|
+
mark: contract.mark.price, gamma: contract.mark.gamma, vega: contract.mark.vega, theta: contract.mark.theta,
|
|
313
|
+
forward: contract.mark.forward, expiresAt: new Date(contract.expiration * 1000).toISOString(),
|
|
314
|
+
} : {}),
|
|
315
|
+
...(contract.coin ? {
|
|
316
|
+
coinCurrency: contract.coin.currency, coinBid: contract.coin.bid, coinAsk: contract.coin.ask,
|
|
317
|
+
coinMark: contract.coin.mark, coinLast: contract.coin.last,
|
|
318
|
+
} : {}),
|
|
239
319
|
};
|
|
240
320
|
});
|
|
241
321
|
}
|
|
242
322
|
|
|
323
|
+
/** A premium in a coin, to the venue's tick (0.0001 BTC); a dash for none. */
|
|
324
|
+
export function formatCoinPremiumCell(value: unknown): string {
|
|
325
|
+
return isFiniteNumber(value) && value > 0 ? value.toFixed(4) : "—";
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** A dollar greek or mark (vega, theta, mark) to cents; a dash for none. */
|
|
329
|
+
export function formatOptionDollarCell(value: unknown): string {
|
|
330
|
+
return isFiniteNumber(value) ? value.toFixed(2) : "—";
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Gamma per 1.00 move, tiny for a coin worth 80,000 dollars: to six places, as the venue shows it to five. */
|
|
334
|
+
export function formatOptionGammaCell(value: unknown): string {
|
|
335
|
+
return isFiniteNumber(value) ? value.toFixed(6) : "—";
|
|
336
|
+
}
|
|
337
|
+
|
|
243
338
|
export function formatOptionQuoteCell(row: Record<string, unknown>, side: "bid" | "ask"): string {
|
|
244
339
|
const quote = optionQuoteSide({ bid: Number(row.bid), ask: Number(row.ask) }, side);
|
|
245
340
|
return quote == null ? "—" : String(quote);
|
|
246
341
|
}
|
|
247
342
|
|
|
248
|
-
/** Implied volatility as a percent; a dash when it could not be solved, never 0.0
|
|
249
|
-
export function formatOptionIvCell(value: unknown): string {
|
|
250
|
-
|
|
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;
|
|
251
348
|
}
|
|
252
349
|
|
|
253
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: [
|
|
@@ -3,6 +3,8 @@ import { apiClient } from "../../api-client";
|
|
|
3
3
|
import { ApiRequestError } from "../../api-client/errors";
|
|
4
4
|
import type { CliCommandDef } from "../../types/plugin";
|
|
5
5
|
import { withCliServices, withMarketData } from "../context";
|
|
6
|
+
import { CLI_ERROR_CODE, failWithFields } from "../errors";
|
|
7
|
+
import type { CliResultColumn } from "../result";
|
|
6
8
|
import { formatCompact } from "../../utils/format";
|
|
7
9
|
import { DATE_OPTION_EXAMPLES, DATE_OPTION_FORMATS, dateOptionError, readCalendarDate } from "../../utils/calendar-date";
|
|
8
10
|
import {
|
|
@@ -21,6 +23,7 @@ import {
|
|
|
21
23
|
type MoverRankBasis,
|
|
22
24
|
} from "../../plugins/builtin/market-movers/model";
|
|
23
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";
|
|
24
27
|
import { fetchEconCalendarFrom } from "../../plugins/builtin/econ/calendar-source";
|
|
25
28
|
import { computedFredSeriesNote, seriesNotOnFred } from "../../sources/gloomberb-cloud/fred-series";
|
|
26
29
|
import { EXTENDED_SESSION_LABELS, getQuoteSessionFields, type ExtendedSession } from "../../market-data/market/status";
|
|
@@ -39,7 +42,8 @@ import {
|
|
|
39
42
|
type CountryFilter,
|
|
40
43
|
type ImpactFilter,
|
|
41
44
|
} from "../../plugins/builtin/econ/calendar-model";
|
|
42
|
-
import { hasNoReleaseTime, hasNoStatedTime } from "../../plugins/builtin/econ/types";
|
|
45
|
+
import { hasNoReleaseTime, hasNoStatedTime, type EconEvent } from "../../plugins/builtin/econ/types";
|
|
46
|
+
import { econRowFigures } from "../../plugins/builtin/econ/figures";
|
|
43
47
|
import { isoDate, rejectExtraArgs, requireArg, requireOneArg, takeFlag, takeOption } from "./command-utils";
|
|
44
48
|
import { buildCorrelationSeries } from "../../plugins/builtin/correlation/matrix/model";
|
|
45
49
|
import { alignDailyCloses, correlateDailyCloses } from "../../plugins/builtin/correlation/compute";
|
|
@@ -70,6 +74,7 @@ import {
|
|
|
70
74
|
fredExportValue,
|
|
71
75
|
fredHistoryNotes,
|
|
72
76
|
fredInterval,
|
|
77
|
+
fredObservesDays,
|
|
73
78
|
fredObservationsFromStart,
|
|
74
79
|
fredProvenance,
|
|
75
80
|
fredPublicationTime,
|
|
@@ -137,7 +142,7 @@ const MOVER_LISTS: Record<string, ScreenerCategory | "trending"> = {
|
|
|
137
142
|
"most-active": "most_actives",
|
|
138
143
|
trending: "trending",
|
|
139
144
|
};
|
|
140
|
-
const MOVERS_USAGE = "movers [gainers|losers|active|trending] [--session regular|extended]";
|
|
145
|
+
const MOVERS_USAGE = "movers [gainers|losers|active|trending] [--session regular|extended] [--min-float <shares>]";
|
|
141
146
|
/** The most rows the movers list serves in one request. */
|
|
142
147
|
const MOVER_REQUEST_CAP = 100;
|
|
143
148
|
/** A one-shot list waits this long for its rows' average volumes and sizes, as `fn MOST` does. */
|
|
@@ -150,6 +155,16 @@ function moverRankBasis(value: string | undefined, ctx: Parameters<CliCommandDef
|
|
|
150
155
|
return basis;
|
|
151
156
|
}
|
|
152
157
|
|
|
158
|
+
/** `--min-float` in shares, null for none; fails on a value that is not a share count. */
|
|
159
|
+
function moverMinFloat(value: string | undefined, ctx: Parameters<CliCommandDef["execute"]>[1]): number | null {
|
|
160
|
+
if (value == null) return null;
|
|
161
|
+
try {
|
|
162
|
+
return minFloatShares(normalizeMinFloat(value));
|
|
163
|
+
} catch (error) {
|
|
164
|
+
ctx.fail(error instanceof Error ? error.message : String(error));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
153
168
|
function screenerCategory(args: readonly string[], ctx: Parameters<CliCommandDef["execute"]>[1]): ScreenerCategory | "trending" {
|
|
154
169
|
rejectExtraArgs(args, 1, { usage: MOVERS_USAGE, takes: "one list" }, ctx);
|
|
155
170
|
const list = args[0]?.toLowerCase() ?? "gainers";
|
|
@@ -195,12 +210,14 @@ async function runMoverCommand(rawArgs: string[], ctx: Parameters<CliCommandDef[
|
|
|
195
210
|
const args = [...rawArgs];
|
|
196
211
|
const sessionOption = takeOption(args, "--session");
|
|
197
212
|
const basis = moverRankBasis(sessionOption, ctx);
|
|
213
|
+
const minFloat = moverMinFloat(takeOption(args, "--min-float"), ctx);
|
|
198
214
|
const category = screenerCategory(args, ctx);
|
|
199
215
|
const limit = ctx.cliOptions.limit ?? 25;
|
|
200
216
|
if (sessionOption != null && (category === "trending" || category === "most_actives")) {
|
|
201
217
|
ctx.fail("--session ranks Gainers and Losers.", category === "trending"
|
|
202
218
|
? "Trending keeps its source's order." : "Most active ranks by volume.");
|
|
203
219
|
}
|
|
220
|
+
if (minFloat != null && category === "trending") ctx.fail("--min-float filters Gainers, Losers and Most active.", "Trending carries no float.");
|
|
204
221
|
if (category === "trending") {
|
|
205
222
|
await withCliServices(ctx, async (services) => {
|
|
206
223
|
const trending = await fetchTrending(limit, undefined, { forceRefresh: ctx.cliOptions.refresh });
|
|
@@ -216,8 +233,8 @@ async function runMoverCommand(rawArgs: string[], ctx: Parameters<CliCommandDef[
|
|
|
216
233
|
}
|
|
217
234
|
|
|
218
235
|
// After the close a price list keeps only the rows moving its way on the basis
|
|
219
|
-
// asked for, so
|
|
220
|
-
const count = category === "most_actives" ? limit : Math.min(MOVER_REQUEST_CAP, Math.max(limit, MOVER_CANDIDATES));
|
|
236
|
+
// asked for, and a minimum float drops rows, so they read more candidates than they show.
|
|
237
|
+
const count = category === "most_actives" && minFloat == null ? limit : Math.min(MOVER_REQUEST_CAP, Math.max(limit, MOVER_CANDIDATES));
|
|
221
238
|
// With the saved session, as MOST reads it: a Pro account gets the real-time lists, anyone else the delayed ones.
|
|
222
239
|
const movers = await withMarketData(ctx, () =>
|
|
223
240
|
fetchPreferredMarketMovers(category, count, { forceRefresh: ctx.cliOptions.refresh, session: basis, metadataWaitMs: MOVER_METADATA_WAIT_MS }));
|
|
@@ -226,7 +243,7 @@ async function runMoverCommand(rawArgs: string[], ctx: Parameters<CliCommandDef[
|
|
|
226
243
|
// the regular session's move (its close's after the close), or with
|
|
227
244
|
// --session extended by the extended print's move from that close.
|
|
228
245
|
const { quotes, rankedBy } = rankMoverQuotes(category, movers.quotes.map(moverRegularSession), basis);
|
|
229
|
-
const rows = quotes.slice(0, limit).map((quote) => {
|
|
246
|
+
const rows = quotes.filter((quote) => meetsMinFloat(quote.floatShares, minFloat)).slice(0, limit).map((quote) => {
|
|
230
247
|
const { extended, ...row } = quote;
|
|
231
248
|
return {
|
|
232
249
|
...row,
|
|
@@ -240,16 +257,18 @@ async function runMoverCommand(rawArgs: string[], ctx: Parameters<CliCommandDef[
|
|
|
240
257
|
// As the MOST report reads it: real-time for a Pro account, delayed otherwise, each row dated by its own last price.
|
|
241
258
|
ctx.printResult({
|
|
242
259
|
data: rows,
|
|
243
|
-
metadata: { category, rankedBy },
|
|
260
|
+
metadata: { category, rankedBy, ...(minFloat != null ? { minFloat } : {}) },
|
|
244
261
|
...(fellBack ? { warnings: ["No pre-market or after-hours prints now, so the list is ranked by the regular-session move."] } : {}),
|
|
245
262
|
freshness: rowsFreshness(rows, { status: movers.source === "cloud" ? "live" : "delayed" }, { stale: movers.stale }),
|
|
246
263
|
}, {
|
|
247
264
|
heading: `Ranked by ${MOVER_RANKING_LABELS[rankedBy]}`,
|
|
265
|
+
...(minFloat != null && hasPartialFloat(rows) ? { footnote: PARTIAL_FLOAT_NOTE } : {}),
|
|
248
266
|
columns: [
|
|
249
267
|
...MOVER_COLUMNS.slice(0, 4),
|
|
250
268
|
...EXTENDED_COLUMNS,
|
|
251
269
|
{ key: "volume", header: "Volume", align: "right", value: (row) => formatCompact(Number(row.volume)) },
|
|
252
270
|
{ ...MARKET_CAP_CELL, value: (row) => row.marketCap == null ? "" : formatCompact(Number(row.marketCap)) },
|
|
271
|
+
...(minFloat != null ? [{ key: "floatShares", header: "Float", align: "right" as const, value: (row: Record<string, unknown>) => formatCompact(Number(row.floatShares)) }] : []),
|
|
253
272
|
],
|
|
254
273
|
});
|
|
255
274
|
}
|
|
@@ -299,6 +318,70 @@ function publisherClock(localTime: unknown, timeZone: unknown): string {
|
|
|
299
318
|
}
|
|
300
319
|
const DAY_MS = 86_400_000;
|
|
301
320
|
|
|
321
|
+
/** The calendar's columns up to the event: shared by the text table and the exports, which keep the raw values under them. */
|
|
322
|
+
function econHeadColumns(viewerZone: string | null): CliResultColumn[] {
|
|
323
|
+
return [
|
|
324
|
+
// Text shows both halves of the event timestamp in UTC, never the host's zone; exports keep the source values.
|
|
325
|
+
{ key: "date", header: "Date", format: (value) => utcDateTimePart(value, "date") },
|
|
326
|
+
// The weekday sits before the clock it is on: the UTC time's, or with --tz the viewer's.
|
|
327
|
+
...viewerZone ? [] : [ECON_DAY_COLUMN],
|
|
328
|
+
// With --tz the viewer's clock has its own column, so the UTC time does not repeat it.
|
|
329
|
+
{ key: "time", header: "Time", format: (value, row) => value === "All Day" ? "All day" : value === ""
|
|
330
|
+
? "" : utcDateTimePart(row.date, "time", !viewerZone) },
|
|
331
|
+
...viewerZone ? [ECON_DAY_COLUMN] : [],
|
|
332
|
+
// Only --tz fills it: the viewer's clock, its weekday in Day before it.
|
|
333
|
+
{ key: "viewerTime", header: viewerZone ? timeZoneCity(viewerZone) : "Viewer", shrink: false,
|
|
334
|
+
format: (value) => typeof value === "string" && viewerZone ? value : "" },
|
|
335
|
+
// Only rows a publisher times itself fill it, so the feeds' calendar prints as before.
|
|
336
|
+
{ key: "localTime", header: "Local", optional: true, dropPriority: 1, format: (value, row) => publisherClock(value, row.timeZone) },
|
|
337
|
+
{ key: "country", header: "Country" },
|
|
338
|
+
{ key: "impact", header: "Impact" },
|
|
339
|
+
{ key: "event", header: "Event" },
|
|
340
|
+
];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* The text table's columns up to the event. Plain output keeps every column a
|
|
345
|
+
* query has, so a column that is empty by construction has to be left out
|
|
346
|
+
* here, not hidden by the table: the viewer's clock only exists with --tz, and
|
|
347
|
+
* the publisher's own clock only on rows a bank or statistics office times
|
|
348
|
+
* itself. Exports keep both, so a script's header never moves.
|
|
349
|
+
*/
|
|
350
|
+
function econTextHeadColumns(viewerZone: string | null, publisherTimed: boolean): CliResultColumn[] {
|
|
351
|
+
return econHeadColumns(viewerZone).filter((column) => (
|
|
352
|
+
column.key === "viewerTime" ? !!viewerZone : column.key === "localTime" ? publisherTimed : true
|
|
353
|
+
));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Actual, Forecast and Prior: as the text draws them (`30.3K`, `-4.1M bbl`) in the table, as
|
|
358
|
+
* numbers in the exports (30300, -4100000), where Unit and Scale say what they count and how they were drawn.
|
|
359
|
+
*/
|
|
360
|
+
function econFigureColumns(text: boolean): CliResultColumn[] {
|
|
361
|
+
return text
|
|
362
|
+
? [{ key: "actualText", header: "Actual" }, { key: "forecastText", header: "Forecast" }, { key: "priorText", header: "Prior" }]
|
|
363
|
+
: [{ key: "actual", header: "Actual" }, { key: "forecast", header: "Forecast" }, { key: "prior", header: "Prior" }];
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const ECON_PUBLISHER_COLUMN: CliResultColumn = {
|
|
367
|
+
key: "publisher", header: "Publisher", format: (value) => typeof value === "string" ? value : "",
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
/** What only the exports say about a row: how to read its figures, who publishes it and where, and the figures as drawn. */
|
|
371
|
+
const econExportColumns: CliResultColumn[] = [
|
|
372
|
+
{ key: "unit", header: "Unit" },
|
|
373
|
+
{ key: "scale", header: "Scale" },
|
|
374
|
+
{ key: "publisher", header: "Publisher" },
|
|
375
|
+
{ key: "sourceUrl", header: "Link" },
|
|
376
|
+
// The figures as the calendar draws them, so a bound (`<1.25%`) that is no number keeps its text.
|
|
377
|
+
{ key: "actualText", header: "actual_text" },
|
|
378
|
+
{ key: "forecastText", header: "forecast_text" },
|
|
379
|
+
{ key: "priorText", header: "prior_text" },
|
|
380
|
+
// Which feed the forecast was read from and when, so a consensus can be told from another read of it.
|
|
381
|
+
{ key: "consensusFeed", header: "consensus_feed" },
|
|
382
|
+
{ key: "consensusReadAt", header: "consensus_read_at" },
|
|
383
|
+
];
|
|
384
|
+
|
|
302
385
|
/**
|
|
303
386
|
* What an empty calendar says. A window that starts after the last day listed
|
|
304
387
|
* says where the listing stops, whatever the filters, rather than nothing.
|
|
@@ -357,6 +440,32 @@ function blankTimeNotes(rows: ReadonlyArray<{ event: string; country: string; ti
|
|
|
357
440
|
return [...byNote].map(([note, events]) => `No time for ${events.join(", ")}: ${note.charAt(0).toLowerCase()}${note.slice(1)}.`);
|
|
358
441
|
}
|
|
359
442
|
|
|
443
|
+
/**
|
|
444
|
+
* Whether a row has come out: it has an actual, or it has no figure at all (a
|
|
445
|
+
* speech, a meeting, a holiday) and its time has passed. A row with a forecast
|
|
446
|
+
* or a prior and no actual is still to come, whatever its time.
|
|
447
|
+
*/
|
|
448
|
+
function cameOut(event: Pick<EconEvent, "actual" | "forecast" | "prior" | "date">, now: number): boolean {
|
|
449
|
+
if (event.actual?.trim()) return true;
|
|
450
|
+
return !event.forecast?.trim() && !event.prior?.trim() && event.date.getTime() <= now;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* A `--from` the stored calendar does not reach back to, which Gloom Cloud refuses
|
|
455
|
+
* with "The calendar reaches back 400 days": an input error naming the earliest
|
|
456
|
+
* day it takes, and `--json` carries it as `earliest`. Any other failure goes on as it was.
|
|
457
|
+
*/
|
|
458
|
+
function failBeforeCalendar(error: unknown, from: string, todayStart: number): never {
|
|
459
|
+
const days = error instanceof ApiRequestError && error.status === 422 ? /reaches back (\d+) days?/i.exec(error.message)?.[1] : undefined;
|
|
460
|
+
if (!days) throw error;
|
|
461
|
+
const earliest = isoDate(new Date(todayStart - Number(days) * DAY_MS)).slice(0, 10);
|
|
462
|
+
return failWithFields(
|
|
463
|
+
`--from ${from} is before the earliest day the calendar reaches back to, ${earliest}. Use --from ${earliest} or later.`,
|
|
464
|
+
CLI_ERROR_CODE,
|
|
465
|
+
{ earliest, days: Number(days) },
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
360
469
|
async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
361
470
|
const rawArgs = [...args];
|
|
362
471
|
const country = parseCountry(takeOption(rawArgs, "--country"), ctx);
|
|
@@ -369,10 +478,13 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
369
478
|
}
|
|
370
479
|
rejectArgs(rawArgs, ECON_USAGE, ctx, "Filter with --country and --impact.");
|
|
371
480
|
await withCliServices(ctx, async () => {
|
|
372
|
-
const
|
|
481
|
+
const now = Date.now();
|
|
482
|
+
const todayStart = Math.floor(now / DAY_MS) * DAY_MS;
|
|
373
483
|
// An earlier day reads the stored calendar, which keeps past events after the week's listing drops them.
|
|
374
484
|
// A server without it, a server error or no network lists the current calendar (cached when offline) instead.
|
|
375
|
-
const stored = fromOption != null && fromOption < isoDate(new Date(todayStart))
|
|
485
|
+
const stored = fromOption != null && fromOption < isoDate(new Date(todayStart))
|
|
486
|
+
? await fetchEconCalendarFrom(fromOption).catch((error: unknown) => failBeforeCalendar(error, fromOption, todayStart))
|
|
487
|
+
: null;
|
|
376
488
|
const past = stored?.events ? stored : null;
|
|
377
489
|
const unread = stored && !stored.events && stored.failure ? `The stored calendar could not be read (${stored.failure}); listing the current calendar` : null;
|
|
378
490
|
const { data: events, fetchedAt, stale } = past ? { data: past.events, fetchedAt: Date.now(), stale: false } : await loadCalendar(ctx.cliOptions.refresh);
|
|
@@ -400,6 +512,8 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
400
512
|
.map((event) => {
|
|
401
513
|
// The viewer's clock (--tz) for a row with a time: its date and time there.
|
|
402
514
|
const clock = viewerZone && !hasNoReleaseTime(event) ? zonedClock(event.date.getTime(), viewerZone) : null;
|
|
515
|
+
// The figures as numbers, scale multiplied out, with their unit and the scale they are drawn in; the text stays as drawn.
|
|
516
|
+
const figures = econRowFigures(event);
|
|
403
517
|
return {
|
|
404
518
|
date: isoDate(event.date),
|
|
405
519
|
// The weekday on the viewer's clock (--tz), else the UTC day's; a row with no time is its day's.
|
|
@@ -409,9 +523,14 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
409
523
|
country: eventCountry(event),
|
|
410
524
|
impact: event.impact,
|
|
411
525
|
event: event.event,
|
|
412
|
-
actual:
|
|
413
|
-
forecast:
|
|
414
|
-
prior:
|
|
526
|
+
actual: figures.actual,
|
|
527
|
+
forecast: figures.forecast,
|
|
528
|
+
prior: figures.prior,
|
|
529
|
+
unit: figures.unit,
|
|
530
|
+
scale: figures.scale,
|
|
531
|
+
actualText: event.actual ?? null,
|
|
532
|
+
forecastText: event.forecast ?? null,
|
|
533
|
+
priorText: event.prior ?? null,
|
|
415
534
|
// The publisher's own clock, for rows Gloom Cloud adds from a bank's or a statistics office's calendar.
|
|
416
535
|
localTime: event.localTime ?? null,
|
|
417
536
|
timeZone: event.timeZone ?? null,
|
|
@@ -420,15 +539,23 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
420
539
|
// Who publishes the figure (Freddie Mac for the 30-year mortgage rate), the period it covers and its page.
|
|
421
540
|
publisher: event.source?.name ?? null,
|
|
422
541
|
referencePeriod: event.referencePeriod ?? null,
|
|
423
|
-
|
|
542
|
+
// When the figure came out: the row's time once it has an actual, and for a row with no figures (a speech, a
|
|
543
|
+
// meeting) once its time has passed; its day alone when it states no time, null before.
|
|
544
|
+
released: cameOut(event, now) ? (hasNoReleaseTime(event) ? isoDate(event.date).slice(0, 10) : isoDate(event.date)) : null,
|
|
424
545
|
sourceUrl: event.source?.url ?? null,
|
|
546
|
+
// The same indicator from one release to the next, as Gloom Cloud names it.
|
|
547
|
+
indicatorId: event.indicatorId ?? null,
|
|
548
|
+
// The feed the forecast was read from and when, for a row with a forecast.
|
|
549
|
+
consensusFeed: event.consensus?.source ?? null,
|
|
550
|
+
consensusReadAt: event.consensus?.readAt ?? null,
|
|
425
551
|
};
|
|
426
552
|
});
|
|
427
553
|
// A filter of countries only, none of which has anything in the window, says so as its empty list.
|
|
428
554
|
const allUnscheduled = unscheduled.length > 0 && unscheduled.length === country.split(",").length;
|
|
429
|
-
const
|
|
555
|
+
const notes = [listedBefore, listedAfter, ...(rows.length > 0 ? blankTimeNotes(rows) : []),
|
|
430
556
|
rows.length > 0 && unscheduled.length > 0 ? noScheduledReleasesMessage(unscheduled) : null]
|
|
431
|
-
.filter((note): note is string => !!note)
|
|
557
|
+
.filter((note): note is string => !!note);
|
|
558
|
+
const footnote = notes.join("\n");
|
|
432
559
|
ctx.printResult({
|
|
433
560
|
data: rows,
|
|
434
561
|
...(unread ? { warnings: [unread] } : {}),
|
|
@@ -440,28 +567,21 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
440
567
|
defaultLimit: 50,
|
|
441
568
|
empty: allUnscheduled ? noScheduledReleasesMessage(unscheduled) : econEmptyMessage(from, listedThrough, listedAfter != null),
|
|
442
569
|
...(footnote ? { footnote } : {}),
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
{ key: "impact", header: "Impact" },
|
|
459
|
-
{ key: "event", header: "Event" },
|
|
460
|
-
{ key: "actual", header: "Actual" },
|
|
461
|
-
{ key: "forecast", header: "Forecast" },
|
|
462
|
-
{ key: "prior", header: "Prior" },
|
|
463
|
-
{ key: "publisher", header: "Publisher", format: (value) => typeof value === "string" ? value : "" },
|
|
464
|
-
],
|
|
570
|
+
// What the text says under the table, a script reads in the CSV's closing lines.
|
|
571
|
+
exportNotes: notes,
|
|
572
|
+
// `as_of` is the release: when a row's figure came out, blank for one that has none yet.
|
|
573
|
+
exportDated: true,
|
|
574
|
+
exportIdentity: (row) => ({
|
|
575
|
+
symbolOrSeries: typeof row.indicatorId === "string" ? row.indicatorId : null,
|
|
576
|
+
// A percent is `pct` here as in `fred` and `yield-curve`; `unit` keeps the `%` the calendar draws.
|
|
577
|
+
currencyOrUnit: row.unit === "%" ? "pct" : typeof row.unit === "string" ? row.unit : null,
|
|
578
|
+
asOf: typeof row.released === "string" ? row.released : null,
|
|
579
|
+
released: typeof row.released === "string" ? row.released : null,
|
|
580
|
+
period: typeof row.referencePeriod === "string" ? row.referencePeriod : null,
|
|
581
|
+
}),
|
|
582
|
+
// The exports add what a script needs to read a row; the text table keeps the columns it had.
|
|
583
|
+
columns: [...econHeadColumns(viewerZone), ...econFigureColumns(false), ...econExportColumns],
|
|
584
|
+
textColumns: [...econTextHeadColumns(viewerZone, rows.some((row) => !!publisherClock(row.localTime, row.timeZone))), ...econFigureColumns(true), ECON_PUBLISHER_COLUMN],
|
|
465
585
|
});
|
|
466
586
|
});
|
|
467
587
|
}
|
|
@@ -545,12 +665,16 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
545
665
|
if (mismatch) ctx.fail(mismatch);
|
|
546
666
|
// Each row's change in the unit its value exports in, and the date it is from; a holiday's stays blank.
|
|
547
667
|
const changes = horizon ? fredChanges(seriesId, data.observations, horizon) : null;
|
|
668
|
+
const releasedOf = (observation: { publisher?: string; publishedAt?: string; releasedOn?: string }) =>
|
|
669
|
+
observation.releasedOn ?? (observation.publisher?.trim() ? observation.publishedAt : undefined);
|
|
548
670
|
// A value FRED does not list yet carries its publisher and when it published, in every export.
|
|
549
|
-
const exportRow = (observation: { date: string; value: number | null; publisher?: string; publishedAt?: string }) => ({
|
|
671
|
+
const exportRow = (observation: { date: string; value: number | null; publisher?: string; publishedAt?: string; releasedOn?: string }) => ({
|
|
550
672
|
date: observation.date,
|
|
551
673
|
value: fredExportValue(seriesId, observation.value),
|
|
552
674
|
...(changes ? { change: changes.get(observation.date)?.change ?? null, changeFrom: changes.get(observation.date)?.changeFrom ?? null } : {}),
|
|
553
675
|
...(observation.publisher?.trim() ? { publisher: observation.publisher.trim(), ...(observation.publishedAt ? { publishedAt: observation.publishedAt } : {}) } : {}),
|
|
676
|
+
// When the figure came out, where the data says: only a series' newest observation does. `date` is the period it covers.
|
|
677
|
+
...releasedOf(observation) ? { released: releasedOf(observation) } : {},
|
|
554
678
|
});
|
|
555
679
|
// A --start before the series begins comes back padded with blank days: the rows start at its first observation.
|
|
556
680
|
const rows = fredObservationsFromStart(data.observations, data.info?.observationStart)
|
|
@@ -570,6 +694,7 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
570
694
|
ctx.fail(`No ${seriesId} observations on or after ${startDate}.`, fredEmptyAdvice(seriesId, latest));
|
|
571
695
|
}
|
|
572
696
|
const periodicity = periodicityOf(data.info?.frequency);
|
|
697
|
+
const observesDays = fredObservesDays(data.info?.frequency);
|
|
573
698
|
const newestDate = rows.reduce<string | null>((newest, row) => (!newest || row.date > newest ? row.date : newest), null);
|
|
574
699
|
// A newest value FRED does not list yet names its publisher, Treasury for a yield; FRED's own stay "FRED".
|
|
575
700
|
const ahead = fredProvenance(data.observations.filter((observation) => observation.date === newestDate), seriesId);
|
|
@@ -598,7 +723,16 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
598
723
|
dateKey: "date",
|
|
599
724
|
empty: fredEmptyMessage(startDate, latest == null ? null : exportRow(latest)),
|
|
600
725
|
...(notes.length ? { footnote: notes.join("\n"), exportNotes: notes } : {}),
|
|
601
|
-
|
|
726
|
+
// `as_of` is the release. A figure with none stated is as of its own day when the series is known to observe
|
|
727
|
+
// days (a daily or weekly rate), and of nothing otherwise, never the first day of the period it covers.
|
|
728
|
+
exportDated: true,
|
|
729
|
+
exportIdentity: (row) => {
|
|
730
|
+
const released = typeof row.released === "string" ? row.released : null;
|
|
731
|
+
const date = typeof row.date === "string" ? row.date : null;
|
|
732
|
+
return { symbolOrSeries: seriesId, currencyOrUnit: unit, interval, asOf: released ?? (observesDays ? date : null), period: date, released };
|
|
733
|
+
},
|
|
734
|
+
// The release closes the row with the other dating columns, so it is not one of the observation's own.
|
|
735
|
+
columns: [...new Set(rows.flatMap((row) => Object.keys(row)))].filter((key) => key !== "released").map((key) => ({ key, header: key })),
|
|
602
736
|
textColumns: [
|
|
603
737
|
{ key: "date", header: "Date" },
|
|
604
738
|
isSpreadSeries(seriesId)
|
|
@@ -738,8 +872,11 @@ export const overviewCliCommands: CliCommandDef[] = [
|
|
|
738
872
|
options: [{
|
|
739
873
|
flags: "--session <basis>",
|
|
740
874
|
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)",
|
|
875
|
+
}, {
|
|
876
|
+
flags: "--min-float <shares>",
|
|
877
|
+
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",
|
|
741
878
|
}],
|
|
742
|
-
examples: ["movers", "movers losers --limit 10", "movers gainers --session extended", "movers trending"],
|
|
879
|
+
examples: ["movers", "movers losers --limit 10", "movers gainers --session extended", "movers gainers --min-float 10M", "movers trending"],
|
|
743
880
|
},
|
|
744
881
|
execute: runMoverCommand,
|
|
745
882
|
},
|