gloomberb 0.16.6 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/api-client/chat-pane-link.ts +47 -0
- package/src/api-client/chat.ts +9 -1
- package/src/api-client/data.ts +42 -2
- package/src/api-client/doe.ts +19 -0
- package/src/api-client/equity-screener.ts +3 -0
- package/src/api-client/europe-energy.ts +91 -0
- package/src/api-client/index.ts +7 -0
- package/src/api-client/letters.ts +41 -0
- package/src/api-client/normalizers.ts +36 -6
- package/src/api-client/organization-tickers.ts +44 -0
- package/src/api-client/request.ts +3 -1
- package/src/api-client/types.ts +27 -1
- package/src/app/runtime/desktop-deeplink.ts +48 -15
- package/src/app/runtime/update.ts +16 -0
- package/src/architecture/core-builtin-allowlist.json +1 -1
- package/src/architecture/dom-cell-metrics-allowlist.json +102 -0
- package/src/ask-gloom/pane-context.ts +23 -19
- package/src/ask-gloom/pane-request.ts +2 -2
- package/src/cli/commands/fred-series.ts +77 -11
- package/src/cli/commands/market.ts +134 -25
- package/src/cli/commands/options-chain.ts +75 -1
- package/src/cli/commands/overview.ts +29 -20
- package/src/cli/commands/ticker.ts +82 -20
- package/src/cli/desktop-pane-shot-view.ts +114 -0
- package/src/cli/desktop-pane-shot.ts +30 -31
- package/src/cli/entry.ts +7 -0
- package/src/cli/export-source.ts +43 -0
- package/src/cli/freshness.ts +18 -3
- package/src/cli/glossary.ts +17 -0
- package/src/cli/history-rows.ts +174 -13
- package/src/cli/index.ts +14 -6
- package/src/cli/known-gaps.ts +14 -3
- package/src/cli/listing-arg.ts +20 -2
- package/src/cli/listing-suggestion.ts +159 -0
- package/src/cli/native-smoke.ts +13 -0
- package/src/cli/not-a-ticker.ts +13 -1
- package/src/cli/pane-functions/capabilities.ts +16 -8
- package/src/cli/pane-functions/catalog.ts +39 -7
- package/src/cli/pane-functions/dom.ts +39 -13
- package/src/cli/pane-functions/function-help.ts +54 -7
- package/src/cli/pane-functions/headless.ts +38 -8
- package/src/cli/pane-functions/index.ts +85 -3
- package/src/cli/pane-functions/lock-note.ts +56 -0
- package/src/cli/pane-functions/report-notices.ts +6 -3
- package/src/cli/pane-functions/report.ts +16 -2
- package/src/cli/pane-functions/resolver.ts +7 -4
- package/src/cli/report-tables.ts +31 -7
- package/src/cli/result.ts +54 -11
- package/src/cli/symbol-rows.ts +18 -1
- package/src/components/chart/composite/axis-overlays.tsx +2 -0
- package/src/components/chart/composite/composite-chart.tsx +4 -21
- package/src/components/chart/composite/legend.tsx +10 -6
- package/src/components/chart/composite/price-axis-labels.tsx +1 -0
- package/src/components/command-bar/list/model.ts +2 -0
- package/src/components/command-bar/list/view.tsx +2 -1
- package/src/components/command-bar/pane-templates/items.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/actions.ts +1 -0
- package/src/components/command-bar/routes/ticker-search/results.ts +3 -1
- package/src/components/command-bar/workflow/ops.ts +8 -97
- package/src/components/command-bar/workflow/pane-reuse.ts +177 -0
- package/src/components/data-table/view.tsx +1 -0
- package/src/components/feed-data-table/stack-view.tsx +18 -42
- package/src/components/index.ts +3 -2
- package/src/components/layout/header.tsx +58 -14
- package/src/components/layout/missing-pane.tsx +2 -0
- package/src/components/layout/shell/chrome.ts +10 -4
- package/src/components/layout/shell/index.tsx +1 -0
- package/src/components/layout/shell/pane/ask.ts +12 -7
- package/src/components/layout/shell/pane/capture.ts +8 -34
- package/src/components/markdown-text.tsx +37 -6
- package/src/components/reader/article-reader.tsx +91 -266
- package/src/components/reader/detail-reader.tsx +210 -0
- package/src/components/reader/reader-text.tsx +239 -0
- package/src/components/table-view-shared.tsx +8 -4
- package/src/components/ticker/badge/list.tsx +15 -4
- package/src/components/ui/choice-dialog.tsx +3 -0
- package/src/components/ui/column-grid.tsx +206 -0
- package/src/components/ui/data-table/types.ts +7 -0
- package/src/components/ui/detail-scroll-body.tsx +31 -4
- package/src/components/ui/display.tsx +86 -4
- package/src/components/ui/fill.ts +8 -0
- package/src/components/ui/icon.tsx +11 -1
- package/src/components/ui/index.ts +2 -0
- package/src/components/ui/list-view.tsx +3 -1
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +9 -1
- package/src/components/ui/select-button.tsx +3 -0
- package/src/data/config/store/fold-panes.ts +202 -11
- package/src/data/config/store/migrations.ts +13 -1
- package/src/docs/snapshot.json +29 -0
- package/src/i18n/es.ts +14 -0
- package/src/i18n/ja.ts +14 -0
- package/src/i18n/ko.ts +14 -0
- package/src/i18n/zh-cn.ts +14 -0
- package/src/i18n/zh-tw.ts +14 -0
- package/src/layout/desk-keys.ts +7 -0
- package/src/layout/desks.ts +2 -2
- package/src/market-data/market/status.ts +67 -6
- package/src/market-data/quotes/contributions.ts +15 -0
- package/src/market-data/quotes/regular-session-valuation.ts +131 -0
- package/src/market-data/quotes/resolution.ts +4 -0
- package/src/market-data/quotes/screener-live-quotes.ts +31 -7
- package/src/market-data/snapshot-provider.ts +6 -3
- package/src/notifications/app-badge.ts +30 -0
- package/src/plugins/builtin/account-management/calendar-tab.tsx +1 -1
- package/src/plugins/builtin/account-management/form-components.tsx +57 -8
- package/src/plugins/builtin/account-management/keyboard.ts +5 -0
- package/src/plugins/builtin/account-management/model.ts +27 -0
- package/src/plugins/builtin/account-management/pane.tsx +62 -2
- package/src/plugins/builtin/account-management/password-dialog.tsx +3 -2
- package/src/plugins/builtin/alerts/cli.ts +6 -4
- package/src/plugins/builtin/analytics/index.tsx +1 -1
- package/src/plugins/builtin/awards/pane.tsx +29 -18
- package/src/plugins/builtin/backtest/pane.tsx +11 -4
- package/src/plugins/builtin/bond-calculator/index.tsx +3 -5
- package/src/plugins/builtin/buildout/detail/company.tsx +11 -7
- package/src/plugins/builtin/buildout/detail/ui.tsx +9 -12
- package/src/plugins/builtin/builtin-plugin-meta.ts +5 -4
- package/src/plugins/builtin/catalysts/model.ts +92 -10
- package/src/plugins/builtin/catalysts/pane.tsx +6 -4
- package/src/plugins/builtin/cds/index.tsx +21 -30
- package/src/plugins/builtin/changelog/index.tsx +11 -12
- package/src/plugins/builtin/chart-composer/headless-schema.ts +2 -0
- package/src/plugins/builtin/chart-composer/headless.ts +87 -5
- package/src/plugins/builtin/chart-composer/pane.tsx +3 -2
- package/src/plugins/builtin/chart-composer/session-history.ts +20 -5
- package/src/plugins/builtin/chat/command-bar.ts +6 -8
- package/src/plugins/builtin/chat/content/composer-runtime.ts +2 -2
- package/src/plugins/builtin/chat/content/composer.tsx +13 -4
- package/src/plugins/builtin/chat/content/footer.tsx +15 -5
- package/src/plugins/builtin/chat/content/handle-prompt.tsx +326 -0
- package/src/plugins/builtin/chat/content/index.tsx +113 -9
- package/src/plugins/builtin/chat/content/layout-metrics.ts +4 -0
- package/src/plugins/builtin/chat/content/open-targets.ts +20 -4
- package/src/plugins/builtin/chat/content/scroll.ts +6 -1
- package/src/plugins/builtin/chat/content/transcript.tsx +11 -2
- package/src/plugins/builtin/chat/ghost-user.ts +1 -1
- package/src/plugins/builtin/chat/handle-prompt.ts +128 -0
- package/src/plugins/builtin/chat/index.tsx +44 -21
- package/src/plugins/builtin/chat/layout.ts +7 -14
- package/src/plugins/builtin/chat/message/desktop.tsx +59 -11
- package/src/plugins/builtin/chat/message/header.tsx +69 -21
- package/src/plugins/builtin/chat/message/pane-chip.tsx +223 -0
- package/src/plugins/builtin/chat/message/person-icons.tsx +53 -0
- package/src/plugins/builtin/chat/message/profile-popover.tsx +73 -9
- package/src/plugins/builtin/chat/message/render-state.ts +15 -2
- package/src/plugins/builtin/chat/message/terminal.tsx +33 -8
- package/src/plugins/builtin/chat/message/types.ts +3 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +149 -0
- package/src/plugins/builtin/chat/pane-links/open.ts +29 -0
- package/src/plugins/builtin/chat/pane-links/previews.ts +119 -0
- package/src/plugins/builtin/chat/pane-state.ts +5 -8
- package/src/plugins/builtin/chat/people.ts +202 -0
- package/src/plugins/builtin/chat/profile-popover.ts +18 -2
- package/src/plugins/builtin/chat/status-widget.tsx +3 -8
- package/src/plugins/builtin/chat/unread-badge.ts +65 -0
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +9 -6
- package/src/plugins/builtin/clinical-trials/client.ts +26 -1
- package/src/plugins/builtin/clinical-trials/index.tsx +10 -2
- package/src/plugins/builtin/clinical-trials/model.ts +269 -0
- package/src/plugins/builtin/clinical-trials/pane.tsx +247 -62
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +156 -0
- package/src/plugins/builtin/cloud/askg/attached-pane.tsx +3 -3
- package/src/plugins/builtin/cloud/askg/pane-attachment.ts +3 -3
- package/src/plugins/builtin/cloud/askg/pane.tsx +2 -1
- package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +3 -2
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +1 -0
- package/src/plugins/builtin/cloud/thesis/detail.tsx +11 -9
- package/src/plugins/builtin/comment-letters/pane.tsx +30 -11
- package/src/plugins/builtin/commodities-energy/index.tsx +179 -0
- package/src/plugins/builtin/commodity-balances/client.ts +208 -0
- package/src/plugins/builtin/commodity-balances/headless.ts +66 -0
- package/src/plugins/builtin/commodity-balances/index.tsx +32 -0
- package/src/plugins/builtin/commodity-balances/model.ts +261 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +227 -0
- package/src/plugins/builtin/composite-plugins.ts +26 -1
- package/src/plugins/builtin/congress-trades/detail.tsx +8 -4
- package/src/plugins/builtin/connections/pane.tsx +16 -5
- package/src/plugins/builtin/cpi/pane.tsx +5 -4
- package/src/plugins/builtin/credit-boards/index.tsx +21 -42
- package/src/plugins/builtin/credit-boards/model.ts +0 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +10 -19
- package/src/plugins/builtin/credit-documents/index.tsx +27 -10
- package/src/plugins/builtin/debt-maturities/index.tsx +3 -17
- package/src/plugins/builtin/debug/index.tsx +2 -2
- package/src/plugins/builtin/dividend-yield/client.ts +3 -1
- package/src/plugins/builtin/dividend-yield/headless.ts +3 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +3 -1
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/docs/blocks.tsx +4 -3
- package/src/plugins/builtin/docs/pane.tsx +16 -8
- package/src/plugins/builtin/doe/index.tsx +11 -4
- package/src/plugins/builtin/doe/pane.tsx +5 -4
- package/src/plugins/builtin/doe/prices.ts +84 -0
- package/src/plugins/builtin/doe/series.ts +4 -1
- package/src/plugins/builtin/earnings/index.tsx +2 -0
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -21
- package/src/plugins/builtin/earnings-calls/index.tsx +2 -0
- package/src/plugins/builtin/earnings-calls/lock-note.ts +17 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +2 -12
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +95 -3
- package/src/plugins/builtin/equity-screener/live.ts +82 -31
- package/src/plugins/builtin/equity-screener/model.ts +121 -68
- package/src/plugins/builtin/equity-screener/pane.tsx +60 -23
- package/src/plugins/builtin/estimate-revisions/client.ts +1 -1
- package/src/plugins/builtin/estimate-revisions/headless.ts +9 -2
- package/src/plugins/builtin/estimate-revisions/index.tsx +3 -0
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +23 -0
- package/src/plugins/builtin/executives/index.tsx +18 -0
- package/src/plugins/builtin/executives/pane.tsx +3 -1
- package/src/plugins/builtin/exposure/charts.tsx +4 -3
- package/src/plugins/builtin/filing-events/index.tsx +18 -0
- package/src/plugins/builtin/fixed-income/index.tsx +46 -0
- package/src/plugins/builtin/fixed-income/model.ts +27 -0
- package/src/plugins/builtin/fixed-income/pane.tsx +163 -0
- package/src/plugins/builtin/fixed-income/shortcut.ts +52 -0
- package/src/plugins/builtin/futures/index.tsx +24 -7
- package/src/plugins/builtin/futures/series.ts +23 -2
- package/src/plugins/builtin/futures/use-front-returns.ts +3 -2
- package/src/plugins/builtin/futures-curve/pane.tsx +6 -4
- package/src/plugins/builtin/gpu/equities.tsx +9 -6
- package/src/plugins/builtin/help/shortcut-table.tsx +4 -2
- package/src/plugins/builtin/insider/detail.tsx +20 -12
- package/src/plugins/builtin/insider/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/format.ts +3 -0
- package/src/plugins/builtin/iv-history/headless.ts +3 -1
- package/src/plugins/builtin/iv-history/index.tsx +6 -1
- package/src/plugins/builtin/iv-history/model.ts +3 -1
- package/src/plugins/builtin/iv-history/screen-pane.tsx +16 -6
- package/src/plugins/builtin/iv-history/universe.ts +1 -1
- package/src/plugins/builtin/jobs/headless.ts +6 -20
- package/src/plugins/builtin/jobs/index.tsx +2 -0
- package/src/plugins/builtin/jobs/lock-note.ts +16 -0
- package/src/plugins/builtin/jodi/client.ts +220 -0
- package/src/plugins/builtin/jodi/headless.ts +63 -0
- package/src/plugins/builtin/jodi/index.tsx +32 -0
- package/src/plugins/builtin/jodi/model.ts +75 -0
- package/src/plugins/builtin/jodi/pane.tsx +141 -0
- package/src/plugins/builtin/market-heatmap/evidence.ts +13 -4
- package/src/plugins/builtin/market-heatmap/headless.ts +3 -3
- package/src/plugins/builtin/market-heatmap/index.tsx +41 -23
- package/src/plugins/builtin/market-heatmap/live.ts +2 -1
- package/src/plugins/builtin/market-heatmap/portfolio.ts +67 -21
- package/src/plugins/builtin/market-movers/table.ts +2 -2
- package/src/plugins/builtin/market-valuation/align.ts +10 -9
- package/src/plugins/builtin/mna/distress/model.ts +52 -1
- package/src/plugins/builtin/mna/distress/pane.tsx +17 -12
- package/src/plugins/builtin/mna/index.tsx +2 -0
- package/src/plugins/builtin/news/wire/index.tsx +2 -2
- package/src/plugins/builtin/news/wire/industry-pane.tsx +28 -14
- package/src/plugins/builtin/news/wire/letters/client.ts +108 -0
- package/src/plugins/builtin/news/wire/letters/model.ts +69 -0
- package/src/plugins/builtin/news/wire/letters/pane.tsx +155 -0
- package/src/plugins/builtin/news/wire/mutes.ts +2 -1
- package/src/plugins/builtin/news/wire/news/query-presets.ts +4 -1
- package/src/plugins/builtin/openfda/client.ts +123 -15
- package/src/plugins/builtin/openfda/model.ts +163 -0
- package/src/plugins/builtin/openfda/pane.tsx +109 -65
- package/src/plugins/builtin/openfda/types.ts +25 -0
- package/src/plugins/builtin/options/index.tsx +2 -1
- package/src/plugins/builtin/options/model-spot.ts +94 -0
- package/src/plugins/builtin/options/open-interest-as-of.ts +63 -0
- package/src/plugins/builtin/options/view.tsx +56 -10
- package/src/plugins/builtin/options-scenario/client.ts +33 -9
- package/src/plugins/builtin/options-scenario/csp.ts +168 -0
- package/src/plugins/builtin/options-scenario/evidence.ts +3 -0
- package/src/plugins/builtin/options-scenario/headless.ts +24 -8
- package/src/plugins/builtin/options-scenario/index.tsx +3 -1
- package/src/plugins/builtin/options-scenario/pane.tsx +32 -6
- package/src/plugins/builtin/pe-band/model.ts +3 -2
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +1 -1
- package/src/plugins/builtin/plugin-module.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +1 -1
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +19 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +2 -2
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +39 -0
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +18 -5
- package/src/plugins/builtin/portfolio-list/live-valuation.ts +25 -13
- package/src/plugins/builtin/power/evidence.ts +32 -3
- package/src/plugins/builtin/power/gas-client.ts +110 -0
- package/src/plugins/builtin/power/gas-model.ts +194 -0
- package/src/plugins/builtin/power/gas.tsx +145 -0
- package/src/plugins/builtin/power/headless.ts +4 -1
- package/src/plugins/builtin/power/index.tsx +19 -9
- package/src/plugins/builtin/power/pane.tsx +2 -3
- package/src/plugins/builtin/power/prices-headless.ts +75 -7
- package/src/plugins/builtin/power/prices.tsx +36 -13
- package/src/plugins/builtin/primary-dealers/client.ts +108 -0
- package/src/plugins/builtin/primary-dealers/headless.ts +53 -0
- package/src/plugins/builtin/primary-dealers/index.tsx +17 -0
- package/src/plugins/builtin/primary-dealers/model.ts +206 -0
- package/src/plugins/builtin/primary-dealers/pane.tsx +149 -0
- package/src/plugins/builtin/relative-rotation/index.tsx +3 -0
- package/src/plugins/builtin/research/analyst-model.ts +9 -5
- package/src/plugins/builtin/research/headless-schema.ts +2 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +68 -32
- package/src/plugins/builtin/research/relative-valuation-model.ts +45 -24
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +39 -4
- package/src/plugins/builtin/risk-factors/index.tsx +18 -0
- package/src/plugins/builtin/risk-factors/pane.tsx +2 -1
- package/src/plugins/builtin/scanner/flow-pane.tsx +8 -1
- package/src/plugins/builtin/scanner/headless.ts +9 -2
- package/src/plugins/builtin/scanner/index.ts +3 -0
- package/src/plugins/builtin/scanner/lock-note.ts +17 -0
- package/src/plugins/builtin/sec/filing-display.tsx +3 -36
- package/src/plugins/builtin/sec/index.tsx +131 -43
- package/src/plugins/builtin/sec/model.ts +148 -2
- package/src/plugins/builtin/sec/row-summaries.ts +105 -0
- package/src/plugins/builtin/sectors/index.tsx +2 -0
- package/src/plugins/builtin/sectors/sector-model.ts +2 -2
- package/src/plugins/builtin/series-inventory.ts +2 -1
- package/src/plugins/builtin/shared/cloud-denied.ts +39 -8
- package/src/plugins/builtin/shared/locked-rows.tsx +1 -1
- package/src/plugins/builtin/shared/pro-lock-note.ts +58 -0
- package/src/plugins/builtin/shared/series-list-detail.tsx +10 -5
- package/src/plugins/builtin/shared/use-quote-board.ts +179 -43
- package/src/plugins/builtin/shared/wall-teaser.tsx +2 -2
- package/src/plugins/builtin/shared/zip-text.ts +46 -0
- package/src/plugins/builtin/short-interest/watch-model.ts +11 -2
- package/src/plugins/builtin/social-mentions/client.ts +3 -3
- package/src/plugins/builtin/social-mentions/headless.ts +6 -2
- package/src/plugins/builtin/social-mentions/index.tsx +2 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +15 -0
- package/src/plugins/builtin/supply-chain/flow.tsx +15 -13
- package/src/plugins/builtin/supply-chain/graph.tsx +6 -4
- package/src/plugins/builtin/supply-chain/pane.tsx +8 -5
- package/src/plugins/builtin/themes/model.ts +2 -2
- package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +25 -1
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +87 -23
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/headless.ts +6 -3
- package/src/plugins/builtin/ticker-detail/overview/components.tsx +105 -104
- package/src/plugins/builtin/ticker-detail/overview/model.ts +12 -3
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +41 -13
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +8 -6
- package/src/plugins/builtin/time-sales/headless.ts +22 -0
- package/src/plugins/builtin/trace-bonds/client.ts +196 -0
- package/src/plugins/builtin/trace-bonds/headless.ts +69 -0
- package/src/plugins/builtin/trace-bonds/index.tsx +33 -0
- package/src/plugins/builtin/trace-bonds/model.ts +115 -0
- package/src/plugins/builtin/trace-bonds/pane.tsx +191 -0
- package/src/plugins/builtin/treasury-auctions/index.tsx +33 -47
- package/src/plugins/builtin/treasury-daily/client.ts +96 -0
- package/src/plugins/builtin/treasury-daily/headless.ts +69 -0
- package/src/plugins/builtin/treasury-daily/index.tsx +17 -0
- package/src/plugins/builtin/treasury-daily/model.ts +69 -0
- package/src/plugins/builtin/treasury-daily/pane.tsx +140 -0
- package/src/plugins/builtin/vol-surface/pane.tsx +6 -3
- package/src/plugins/builtin/world-venue-map/entity-detail.tsx +15 -2
- package/src/plugins/builtin/world-venue-map/map.tsx +1 -0
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/catalog-browser.ts +25 -2
- package/src/plugins/marketplace/model.ts +57 -21
- package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
- package/src/renderers/dom/build-assets.ts +14 -5
- package/src/renderers/dom/data-table/index.tsx +53 -18
- package/src/renderers/dom/data-table/row.tsx +1 -1
- package/src/renderers/dom/desktop/controls.tsx +2 -1
- package/src/renderers/dom/desktop/icons.tsx +25 -0
- package/src/renderers/dom/desktop/list-view.tsx +1 -1
- package/src/renderers/dom/desktop/menu.tsx +1 -1
- package/src/renderers/dom/desktop/popover.tsx +6 -15
- package/src/renderers/dom/desktop/query-bar.tsx +3 -1
- package/src/renderers/dom/host/chart-surface.tsx +7 -2
- package/src/renderers/dom/host/input.tsx +27 -3
- package/src/renderers/dom/host/resize-rows.ts +111 -0
- package/src/renderers/dom/host/sticky-scroll.ts +22 -3
- package/src/renderers/dom/host/style.ts +14 -0
- package/src/renderers/dom/host/surface-3d.tsx +8 -5
- package/src/renderers/dom/host/surface-fit.ts +10 -6
- package/src/renderers/dom/host/text.tsx +2 -2
- package/src/renderers/dom/styles.css +85 -10
- package/src/renderers/electrobun/bun/desktop/app-badge.ts +37 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +8 -0
- package/src/renderers/electrobun/bun/desktop/macos-dock-badge.ts +37 -0
- package/src/renderers/electrobun/bun/desktop/macos-dock-icon.ts +2 -40
- package/src/renderers/electrobun/bun/desktop/macos-objc.ts +41 -0
- package/src/renderers/electrobun/bun/tui-entry.ts +18 -1
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/app-badge-host.ts +17 -0
- package/src/renderers/electrobun/view/app-services.ts +2 -22
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +8 -3
- package/src/renderers/electrobun/view/main.tsx +2 -0
- package/src/renderers/electrobun/view/news-source.ts +42 -0
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +5 -1
- package/src/series/validate.ts +6 -1
- package/src/shares/api.ts +5 -1
- package/src/shares/dialog.tsx +2 -1
- package/src/shares/location.ts +31 -0
- package/src/shares/open.ts +26 -0
- package/src/shares/pane-image.ts +18 -6
- package/src/shares/personal-pane.ts +42 -54
- package/src/sources/gloomberb-cloud/index.ts +7 -2
- package/src/sources/gloomberb-cloud/normalizers.ts +5 -0
- package/src/sources/history-coverage.ts +22 -4
- package/src/sources/history-result.ts +3 -1
- package/src/sources/provider-errors.ts +2 -1
- package/src/sources/provider-router/batches.ts +18 -2
- package/src/sources/provider-router/financials.ts +5 -0
- package/src/sources/provider-router/index.ts +2 -1
- package/src/state/app/context/index.tsx +3 -2
- package/src/test-support/builtin-panes.ts +31 -0
- package/src/tickers/listing-venues.ts +22 -0
- package/src/tickers/search/index.ts +7 -2
- package/src/tickers/search/ranking.ts +19 -0
- package/src/time-series/chart-data.ts +2 -0
- package/src/time-series/parsed-history-cache.ts +41 -11
- package/src/time-series/resolve.ts +47 -15
- package/src/types/config.ts +1 -1
- package/src/types/data-provider.ts +13 -1
- package/src/types/financials.ts +35 -0
- package/src/types/headless.ts +22 -0
- package/src/types/plugin.ts +46 -6
- package/src/types/price-history.ts +2 -0
- package/src/ui/host.tsx +6 -0
- package/src/utils/crypto-pair.ts +1 -1
- package/src/utils/currency-pair.ts +66 -0
- package/src/utils/decimal-comma.ts +57 -0
- package/src/utils/financial-statements.ts +25 -0
- package/src/utils/fundamentals.ts +41 -1
- package/src/utils/guards.ts +25 -0
- package/src/utils/local-utc-offset.ts +37 -0
- package/src/utils/plugin-proxy-hosts.json +8 -1
- package/src/components/chart/composite/crosshair-readout.tsx +0 -150
- package/src/plugins/builtin/chat/message/ai-badge.tsx +0 -18
- package/src/plugins/builtin/credit-boards/pane.tsx +0 -30
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
getExtendedSessionDisplay,
|
|
17
17
|
getQuoteSessionFields,
|
|
18
18
|
getRegularSessionDisplay,
|
|
19
|
-
|
|
19
|
+
quoteSessionLabel,
|
|
20
20
|
type ExtendedSession,
|
|
21
21
|
} from "../../market-data/market/status";
|
|
22
22
|
import { formatCompact, formatDistributionAmount, formatPercent, formatPercentRaw } from "../../utils/format";
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
formatPriceRange,
|
|
32
32
|
} from "../helpers";
|
|
33
33
|
import { cliStyles, colorBySign, renderSection, renderStats } from "../../utils/cli-output";
|
|
34
|
+
import { statementHistoryNotes } from "../../utils/financial-statements";
|
|
34
35
|
import { formatPerShareNumber } from "../../utils/reported-money";
|
|
35
36
|
import {
|
|
36
37
|
analystTargetCurrency,
|
|
@@ -48,9 +49,9 @@ import type { FuturesContractTermsPayload } from "../../api-client/futures-curve
|
|
|
48
49
|
import { futuresSettleText, isFuturesInstrument } from "../../market-data/futures-contract";
|
|
49
50
|
import {
|
|
50
51
|
chainHasExpiry,
|
|
51
|
-
chainWithModelFigures,
|
|
52
52
|
formatExpiryList,
|
|
53
53
|
loadOptionModelInputs,
|
|
54
|
+
loadOptionsChainOpenInterest,
|
|
54
55
|
formatOptionDeltaCell,
|
|
55
56
|
formatOptionIvCell,
|
|
56
57
|
formatOptionQuoteCell,
|
|
@@ -58,10 +59,16 @@ import {
|
|
|
58
59
|
OPTION_MODEL_RATE,
|
|
59
60
|
OPTIONS_USAGE,
|
|
60
61
|
optionRows,
|
|
62
|
+
optionsBasisFields,
|
|
63
|
+
optionsBasisFootnote,
|
|
61
64
|
parseOptionExpiration,
|
|
65
|
+
requestListedOpenInterest,
|
|
66
|
+
valueChainAtModelSpot,
|
|
67
|
+
type OptionsChainBasis,
|
|
62
68
|
} from "./options-chain";
|
|
63
69
|
import { DEFAULT_LEAPS_CRITERIA, LEAPS_USAGE, parseLeapsCriteria, runLeapsScreen, takeLeapsOptions } from "./options-leaps";
|
|
64
70
|
import type { CliResultColumn } from "../result";
|
|
71
|
+
import type { PriceHistoryResult } from "../../types/price-history";
|
|
65
72
|
import {
|
|
66
73
|
exportRowsTable,
|
|
67
74
|
reportFooterLines,
|
|
@@ -70,9 +77,12 @@ import {
|
|
|
70
77
|
} from "../report-tables";
|
|
71
78
|
import {
|
|
72
79
|
historyBarStatusNote,
|
|
80
|
+
historyCoverageWarning,
|
|
73
81
|
historyFacts,
|
|
74
82
|
historyFlagNote,
|
|
75
83
|
historyGapNote,
|
|
84
|
+
historyJumpNote,
|
|
85
|
+
historyUnbrokenFrom,
|
|
76
86
|
historyAllRangeYears,
|
|
77
87
|
historyIntervalsByRange,
|
|
78
88
|
historyNotes,
|
|
@@ -89,7 +99,9 @@ import { newsFallbackText, newsSearchHeading, newsSearchText, newsSearchTextProb
|
|
|
89
99
|
import { formatUtcTime } from "../../utils/utc-time";
|
|
90
100
|
import { isFiniteNumber } from "../../utils/guards";
|
|
91
101
|
import { exportedFundamentals } from "../../utils/price-earnings";
|
|
92
|
-
import { fundamentalsFreshness, fxPairFreshness, quotesFreshness, rowsFreshness } from "../freshness";
|
|
102
|
+
import { fundamentalsFreshness, fxPairFreshness, quotesFreshness, rowsFreshness, valuationFreshness } from "../freshness";
|
|
103
|
+
import { atRegularSessionPrice } from "../../market-data/quotes/regular-session-valuation";
|
|
104
|
+
import { selectMarketCapitalization } from "../../utils/market-capitalization";
|
|
93
105
|
import {
|
|
94
106
|
barHistoryFreshness,
|
|
95
107
|
barResolutionFromDates,
|
|
@@ -128,13 +140,15 @@ import {
|
|
|
128
140
|
import { indexRootAlsoLine, indexRootFor, indexRootTryLine, isYieldIndexSymbol } from "../index-roots";
|
|
129
141
|
import { earningsCalendarFailure, earningsChoiceNote, earningsDateLabel, isListingAbroad, noListingCalendarNote } from "../earnings-calendar";
|
|
130
142
|
import { formatBasisPoints, toBasisPoints } from "../../utils/basis-points";
|
|
131
|
-
import { providerMissReason } from "../../sources/provider-errors";
|
|
132
|
-
import { isNotATickerMessage } from "../not-a-ticker";
|
|
143
|
+
import { notATickerSymbol, providerMissReason } from "../../sources/provider-errors";
|
|
144
|
+
import { isNotATickerMessage, withListingSuggestion } from "../not-a-ticker";
|
|
145
|
+
import { describeSuggestion, suggestListing } from "../listing-suggestion";
|
|
133
146
|
import { splitSymbolArgs } from "../symbol-args";
|
|
134
147
|
import { cliPerpetualContract, perpetualContractStats, type PerpetualContractPayload } from "../perpetual-contract";
|
|
135
148
|
import { subMinuteDataMessage } from "../known-gaps";
|
|
136
149
|
import { secRegistrantMismatchMessage, SecRegistrantMismatchError, areDifferentCompanies } from "../../sources/sec-registrant";
|
|
137
150
|
import { isCryptoPairSymbol } from "../../utils/crypto-pair";
|
|
151
|
+
import { looksLikeCurrencyPair } from "../../utils/currency-pair";
|
|
138
152
|
import { isUsListingExchange } from "../../utils/exchanges";
|
|
139
153
|
import { describeTickerChange, startTickerChangeLookup, tickerChangeLine, type TickerChange } from "../../tickers/ticker-changes";
|
|
140
154
|
import { noSecReportsNotice, nonUsSecListingVenue } from "../../utils/sec";
|
|
@@ -245,6 +259,7 @@ const QUOTE_LEAD_COLUMNS = [
|
|
|
245
259
|
// Text drops a column no row fills, so a table without an extended print stays as narrow as before.
|
|
246
260
|
extendedColumn("PRE"),
|
|
247
261
|
extendedColumn("POST"),
|
|
262
|
+
extendedColumn("OVERNIGHT"),
|
|
248
263
|
{ key: "session", header: "Session", shrink: false },
|
|
249
264
|
];
|
|
250
265
|
|
|
@@ -261,17 +276,21 @@ function yieldChangeCell(row: QuoteRow): string {
|
|
|
261
276
|
return row.changeBasisPoints == null ? "" : colorBySign(formatBasisPoints(row.changeBasisPoints / 100, 1), row.changeBasisPoints);
|
|
262
277
|
}
|
|
263
278
|
|
|
264
|
-
/**
|
|
279
|
+
/**
|
|
280
|
+
* The pre-market, after-hours or overnight print and its move from the
|
|
281
|
+
* regular close, in its own column; "mid" when it is a bid and ask midpoint.
|
|
282
|
+
*/
|
|
265
283
|
function extendedColumn(session: ExtendedSession) {
|
|
284
|
+
const price = (row: QuoteRow) => row.extendedMark ? `${row.extendedPrice} mid` : row.extendedPrice;
|
|
266
285
|
return {
|
|
267
|
-
key: session === "PRE" ? "preMarket" : "afterHours",
|
|
286
|
+
key: session === "PRE" ? "preMarket" : session === "POST" ? "afterHours" : "overnight",
|
|
268
287
|
header: EXTENDED_SESSION_LABELS[session],
|
|
269
288
|
align: "right" as const,
|
|
270
289
|
value: (row: QuoteRow) => row.extendedSession === session && row.extendedPrice
|
|
271
|
-
? [row
|
|
290
|
+
? [price(row), row.extendedChangePercent == null ? "" : formatPercentRaw(row.extendedChangePercent)].join(" ").trim()
|
|
272
291
|
: "",
|
|
273
292
|
format: (value: unknown, row: QuoteRow) => (
|
|
274
|
-
value ? [row
|
|
293
|
+
value ? [price(row), formatChangePercentCell(row.extendedChangePercent)].join(" ").trim() : ""
|
|
275
294
|
),
|
|
276
295
|
};
|
|
277
296
|
}
|
|
@@ -361,7 +380,9 @@ function quoteRows(results: QuoteCliRecord[], now = Date.now()) {
|
|
|
361
380
|
rawExtendedPrice: extended?.price ?? null,
|
|
362
381
|
extendedChange: extended?.change ?? null,
|
|
363
382
|
extendedChangePercent: roundedPercent(extended?.changePercent),
|
|
364
|
-
session
|
|
383
|
+
// The extended price is the session's bid and ask midpoint, nothing having traded in it.
|
|
384
|
+
extendedMark: extended?.mark === true,
|
|
385
|
+
session: quoteSessionLabel(quote) ?? "",
|
|
365
386
|
// The close the shown move is measured from: the one before the session Last is.
|
|
366
387
|
previousClose: price(display?.change != null ? display.price - display.change : quote?.previousClose),
|
|
367
388
|
dayRange: quote?.low != null && quote.high != null
|
|
@@ -696,7 +717,7 @@ async function answerQuote(
|
|
|
696
717
|
root ? indexRootTryLine(root, request.command) : null,
|
|
697
718
|
other || listing.exchange ? null : futuresTryLine(listing.symbol, request),
|
|
698
719
|
].filter((line): line is string => !!line);
|
|
699
|
-
if (!other && tryLines.length === 0) return { result, listing, notes: [] };
|
|
720
|
+
if (!other && tryLines.length === 0) return { result: await withCurrencyPairHint(result, market), listing, notes: [] };
|
|
700
721
|
const message = other?.message ?? `${listing.symbol} has no ${request.noun}.`;
|
|
701
722
|
const details = [other?.details, ...tryLines].filter(Boolean).join("\n");
|
|
702
723
|
const oneLine = [
|
|
@@ -707,6 +728,21 @@ async function answerQuote(
|
|
|
707
728
|
return { result, listing, failure: { message, details, oneLine }, notes: [] };
|
|
708
729
|
}
|
|
709
730
|
|
|
731
|
+
/**
|
|
732
|
+
* A symbol written like a currency pair that is not a ticker (USD_NGN, as some
|
|
733
|
+
* FX feeds write a pair) names the listing `gloomberb search` finds nearest to
|
|
734
|
+
* it, as `watchlist add` does: "Did you mean USDNGN=X (US Dollar/Nigerian
|
|
735
|
+
* Naira)?". When search finds nothing near, the row keeps its own error and its
|
|
736
|
+
* search hint.
|
|
737
|
+
*/
|
|
738
|
+
async function withCurrencyPairHint(result: QuoteBatchResult, market: MarketContext): Promise<QuoteBatchResult> {
|
|
739
|
+
const message = errorMessage(result.error);
|
|
740
|
+
const symbol = message ? notATickerSymbol(message) : null;
|
|
741
|
+
if (!message || !symbol || !looksLikeCurrencyPair(symbol)) return result;
|
|
742
|
+
const suggestion = await suggestListing(symbol, market);
|
|
743
|
+
return suggestion ? { ...result, error: withListingSuggestion(message, describeSuggestion(suggestion)) } : result;
|
|
744
|
+
}
|
|
745
|
+
|
|
710
746
|
/** A crypto pair that will not load points at the crypto board. Structured formats keep the error as thrown. */
|
|
711
747
|
function failHistory(error: unknown, symbol: string, ctx: Parameters<CliCommandDef["execute"]>[1]): never {
|
|
712
748
|
if (ctx.cliOptions.format !== "text" || !isCryptoPairSymbol(symbol)) throw error;
|
|
@@ -744,16 +780,17 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
744
780
|
const context = { cacheMode: ctx.cliOptions.refresh ? "refresh" as const : "default" as const };
|
|
745
781
|
const load = () => market.dataProvider.getPriceHistoryWithMetadata
|
|
746
782
|
? market.dataProvider.getPriceHistoryWithMetadata(symbol, exchange, range, context)
|
|
747
|
-
: market.dataProvider.getPriceHistory(symbol, exchange, range, context).then((points) => ({ points, resolution: null }));
|
|
783
|
+
: market.dataProvider.getPriceHistory(symbol, exchange, range, context).then((points): PriceHistoryResult => ({ points, resolution: null }));
|
|
748
784
|
const quoted = loadListingQuote(market.dataProvider, listing);
|
|
749
785
|
const fate = listingFateLoader(listing, market, quoted);
|
|
750
|
-
const [
|
|
786
|
+
const [loaded, quote] = await Promise.all([
|
|
751
787
|
loadForListing(listing, market, ctx, load, (loaded) => loaded.points.length === 0, {
|
|
752
788
|
command: range === "1Y" ? "history" : `history --range ${range}`, noun: "history", fate,
|
|
753
789
|
})
|
|
754
790
|
.catch((error) => failHistory(error, listing.key, ctx)),
|
|
755
791
|
quoted,
|
|
756
792
|
]);
|
|
793
|
+
const { points, resolution } = loaded;
|
|
757
794
|
const identity = listingIdentity(listing, quote);
|
|
758
795
|
// Without a quote currency, the listing metadata research reads (as `ticker` does) may still state it.
|
|
759
796
|
const listed = quote?.currency ? quote : await market.dataProvider.getQuoteMetadata?.(symbol, exchange).catch(() => null);
|
|
@@ -783,7 +820,8 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
783
820
|
// FB's bars are the fund listed there now; META's from 2021 are from when it was FB.
|
|
784
821
|
...listingFateNotes(await fate(), { served: data.length > 0, firstDate: data[0]?.date }),
|
|
785
822
|
...await listingChoiceNotes(listing, market, quote, venues),
|
|
786
|
-
...historyNotes(range, interval, data[0]?.date),
|
|
823
|
+
...historyNotes(range, interval, data[0]?.date), historyCoverageWarning(range, loaded) ?? [],
|
|
824
|
+
gaps ?? [], historyJumpNote(data) ?? [], historyFlagNote(data) ?? [], historyBarStatusNote(data) ?? [],
|
|
787
825
|
].flat();
|
|
788
826
|
const priceColumns: CliResultColumn[] = [
|
|
789
827
|
intraday
|
|
@@ -821,6 +859,8 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
821
859
|
interval,
|
|
822
860
|
firstDate: data[0]?.date ?? null,
|
|
823
861
|
lastDate: data.at(-1)?.date ?? null,
|
|
862
|
+
// The first bar after the last gap or jump; the first bar when there is none.
|
|
863
|
+
unbrokenFrom: historyUnbrokenFrom(data) ?? data[0]?.date ?? null,
|
|
824
864
|
bars: data.length,
|
|
825
865
|
asOf: freshness?.asOf ?? null,
|
|
826
866
|
...(volume ? { volumeUnit: volume.unit, volumePeriod: interval === "1d" ? "UTC day" : interval } : {}),
|
|
@@ -831,16 +871,14 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
831
871
|
heading: `${listingHeading(identity)}\n${cliStyles.muted(historyFacts(unit, data).join(" · "))}`,
|
|
832
872
|
dateKey: "date",
|
|
833
873
|
textColumns: [...priceColumns, flagTextColumn],
|
|
834
|
-
// Every exported row says its currency, what a price is in and its bar size, so it survives head and concatenation.
|
|
835
874
|
columns: [
|
|
836
875
|
...priceColumns,
|
|
837
|
-
{ key: "currency", header: "Currency" },
|
|
838
|
-
{ key: "unit", header: "Unit" },
|
|
839
|
-
{ key: "interval", header: "Interval" },
|
|
840
876
|
flagColumn,
|
|
841
877
|
...marked ? [{ key: "partial", header: "Partial" }, { key: "filled", header: "Filled" }] : [],
|
|
842
878
|
...volume ? [{ key: "volumeUnit", header: "Volume unit" }, { key: "volumeNote", header: "Volume note" }] : [],
|
|
843
879
|
],
|
|
880
|
+
// Every exported row says what it is a bar of, the currency a price is in and its bar size, so it survives head and concatenation.
|
|
881
|
+
exportIdentity: { symbolOrSeries: listing.key, currencyOrUnit: unit.currency ?? unit.unit, interval },
|
|
844
882
|
});
|
|
845
883
|
});
|
|
846
884
|
}
|
|
@@ -889,15 +927,28 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
889
927
|
...financials,
|
|
890
928
|
};
|
|
891
929
|
if (view !== "statements") {
|
|
892
|
-
|
|
930
|
+
// A valuation is measured at the quote's regular session price, so the quote dates it.
|
|
931
|
+
const freshness = view === "valuation" ? valuationFreshness(financials) : fundamentalsFreshness(financials);
|
|
932
|
+
// Market cap, the multiples and the yield at the regular session price, as the text gives them; the quote stays as sent.
|
|
933
|
+
const priced = atRegularSessionPrice(financials);
|
|
934
|
+
const capitalization = selectMarketCapitalization(priced.quote, priced.fundamentals);
|
|
893
935
|
ctx.printResult({
|
|
894
936
|
// JSON reads a multiple over a loss as null with its reason, never as a number.
|
|
895
|
-
data: {
|
|
937
|
+
data: {
|
|
938
|
+
...data,
|
|
939
|
+
...view === "valuation" ? {
|
|
940
|
+
...getQuoteSessionFields(financials.quote),
|
|
941
|
+
marketCap: capitalization?.value ?? null,
|
|
942
|
+
marketCapCurrency: capitalization?.currency ?? null,
|
|
943
|
+
} : {},
|
|
944
|
+
fundamentals: exportedFundamentals(priced.fundamentals),
|
|
945
|
+
},
|
|
896
946
|
freshness,
|
|
897
947
|
...(choice.length > 0 ? { warnings: choice } : {}),
|
|
898
948
|
}, {
|
|
899
949
|
text: () => renderFundamentalsReport(data, view),
|
|
900
950
|
...(tabular ? { tables: selectTables(fundamentalsReportTables(data, view, freshness)) } : {}),
|
|
951
|
+
exportIdentity: { symbolOrSeries: listing.key },
|
|
901
952
|
});
|
|
902
953
|
return;
|
|
903
954
|
}
|
|
@@ -905,11 +956,13 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
905
956
|
const freshness = rowsFreshness(rows, {
|
|
906
957
|
...REPORTED_DATA, basis: "financial statements", observedKey: "date", oldest: null,
|
|
907
958
|
});
|
|
959
|
+
// The rows are the annual years served; older ones are not, and the closing lines say so.
|
|
960
|
+
const historyNotes = statementHistoryNotes("annual", rows.map((row) => row.date), listing.key);
|
|
908
961
|
// NDJSON keeps the raw statement rows it always wrote; CSV gets the table and its closing lines.
|
|
909
962
|
const tables = tabular
|
|
910
963
|
? selectTables({
|
|
911
964
|
tables: [exportRowsTable("Annual statements", STATEMENT_COLUMNS, rows)],
|
|
912
|
-
footer: reportFooterLines({ freshness }),
|
|
965
|
+
footer: reportFooterLines({ freshness, notes: historyNotes }),
|
|
913
966
|
})
|
|
914
967
|
: undefined;
|
|
915
968
|
ctx.printResult({
|
|
@@ -921,6 +974,7 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
921
974
|
quarterlyStatements: financials.quarterlyStatements.length,
|
|
922
975
|
fundamentals: exportedFundamentals(financials.fundamentals),
|
|
923
976
|
profile: financials.profile,
|
|
977
|
+
...(historyNotes.length > 0 ? { notes: historyNotes } : {}),
|
|
924
978
|
},
|
|
925
979
|
freshness,
|
|
926
980
|
...(choice.length > 0 ? { warnings: choice } : {}),
|
|
@@ -928,7 +982,10 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
928
982
|
heading: listingHeading(identity),
|
|
929
983
|
rows: () => rows,
|
|
930
984
|
columns: STATEMENT_COLUMNS,
|
|
985
|
+
...(historyNotes.length > 0 ? { footnote: historyNotes.join("\n"), exportNotes: historyNotes } : {}),
|
|
931
986
|
...(tables && ctx.cliOptions.format === "csv" ? { tables } : {}),
|
|
987
|
+
// Annual statements in the company's reporting currency.
|
|
988
|
+
exportIdentity: { symbolOrSeries: listing.key, currencyOrUnit: rows.find((row) => row.currency)?.currency ?? null, interval: "annual" },
|
|
932
989
|
});
|
|
933
990
|
});
|
|
934
991
|
}
|
|
@@ -1318,6 +1375,7 @@ async function runHolders(
|
|
|
1318
1375
|
tables: [exportRowsTable("Holders", columns, shownRows)],
|
|
1319
1376
|
footer: reportFooterLines({ freshness, notes: notices }),
|
|
1320
1377
|
},
|
|
1378
|
+
exportIdentity: { symbolOrSeries: listing.key },
|
|
1321
1379
|
summary: () => [
|
|
1322
1380
|
[valueBasis ? `Value = ${valueBasis}.` : "", positionsBasis ?? "", shareBasisNote ?? "", unvaluedNote ?? ""].filter(Boolean).join(" "),
|
|
1323
1381
|
insiderCommand ? insiderSummary(data, ownerTypes, listing, identity, form4) : "",
|
|
@@ -1447,6 +1505,8 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1447
1505
|
const quotePromise = loadListingQuote(market.dataProvider, listing);
|
|
1448
1506
|
const fate = listingFateLoader(listing, market, quotePromise);
|
|
1449
1507
|
const inputsPromise = loadOptionModelInputs(market.dataProvider, listing, quotePromise, refresh);
|
|
1508
|
+
// OPX's listing dates the chain's open interest; asked for alongside the chain when the expiry is known.
|
|
1509
|
+
const listedOpenInterest = expirationDate != null ? requestListedOpenInterest(symbol, expirationDate) : null;
|
|
1450
1510
|
const { result, chain: loaded } = await loadForListing(listing, market, ctx, async () => {
|
|
1451
1511
|
const cached = await market.dataProvider.getCachedQuery?.("getOptionsChain", [symbol, exchange, expirationDate, undefined])
|
|
1452
1512
|
.load({ force: refresh });
|
|
@@ -1476,7 +1536,13 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1476
1536
|
}
|
|
1477
1537
|
const quote = await quotePromise;
|
|
1478
1538
|
const identity = listingIdentity(listing, quote);
|
|
1479
|
-
|
|
1539
|
+
// IV and delta are valued at one spot and instant, named under the table and on every exported row.
|
|
1540
|
+
const { chain, spot: modelSpot } = valueChainAtModelSpot(loaded, inputs, Date.now(), identity.exchange);
|
|
1541
|
+
const basis: OptionsChainBasis = { spot: modelSpot, dividendYield: inputs.dividendYield ?? null,
|
|
1542
|
+
openInterest: await loadOptionsChainOpenInterest(symbol, chain, listedOpenInterest) };
|
|
1543
|
+
const basisFields = optionsBasisFields(basis);
|
|
1544
|
+
const currency = [...chain.calls, ...chain.puts].find((contract) => contract.currency?.trim())?.currency.trim().toUpperCase()
|
|
1545
|
+
?? quote?.currency ?? null;
|
|
1480
1546
|
// A failed refresh falls back to the stored chain, which can be days old.
|
|
1481
1547
|
const refreshWarning = result?.refreshError == null ? null
|
|
1482
1548
|
: `Options refresh failed; showing the chain stored ${new Date(result.fetchedAt).toISOString()}`
|
|
@@ -1498,23 +1564,54 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1498
1564
|
...(chain.delayMinutes != null ? { delayMinutes: chain.delayMinutes } : {}),
|
|
1499
1565
|
stale: refreshWarning != null,
|
|
1500
1566
|
});
|
|
1567
|
+
const contractCount = chain.calls.length + chain.puts.length;
|
|
1501
1568
|
ctx.printResult({
|
|
1502
1569
|
data: chain,
|
|
1503
1570
|
metadata: {
|
|
1504
1571
|
...listingMetadata(identity),
|
|
1505
1572
|
expirations: chain.expirationDates,
|
|
1573
|
+
currency,
|
|
1574
|
+
interval: OPTIONS_EXPORT_INTERVAL,
|
|
1575
|
+
// `iv` is annualised volatility and `delta` the option's change per 1.00 move in the underlying, both decimals.
|
|
1576
|
+
units: { iv: "annualized decimal", delta: "decimal per 1.00 move in the underlying" },
|
|
1506
1577
|
// What `iv` and `delta` on each contract are valued from; the provider's own impliedVolatility is left as sent.
|
|
1507
|
-
model: {
|
|
1578
|
+
model: {
|
|
1579
|
+
spot: basisFields.spot, spotBasis: basisFields.spotBasis, spotAsOf: basisFields.spotAsOf,
|
|
1580
|
+
valuationTime: modelSpot ? new Date(modelSpot.valuationTime).toISOString() : null,
|
|
1581
|
+
dividendYield: inputs.dividendYield ?? null, rate: OPTION_MODEL_RATE,
|
|
1582
|
+
},
|
|
1583
|
+
openInterest: { asOf: basisFields.openInterestAsOf, listedAsOf: basis.openInterest?.listedDate ?? null,
|
|
1584
|
+
compared: basis.openInterest?.compared ?? 0, matched: basis.openInterest?.matched ?? 0 },
|
|
1508
1585
|
},
|
|
1509
1586
|
...(warnings.length > 0 ? { warnings } : {}),
|
|
1510
1587
|
freshness,
|
|
1511
1588
|
}, {
|
|
1512
1589
|
heading: listingHeading(identity),
|
|
1513
|
-
rows: optionRows,
|
|
1590
|
+
rows: (data: OptionsChain) => optionRows(data).map((row) => ({ ...row, ...basisFields })),
|
|
1514
1591
|
// Choosing an expiry needs the list; once one is chosen the table is that expiry.
|
|
1515
1592
|
summary: (data: OptionsChain) => expirationDate == null ? formatExpiryList(data.expirationDates) : "",
|
|
1516
1593
|
empty: `No option contracts for ${listingTitle(identity)}.`,
|
|
1594
|
+
...(contractCount > 0 ? { footnote: optionsBasisFootnote(basis) } : {}),
|
|
1595
|
+
exportIdentity: { symbolOrSeries: listing.key, currencyOrUnit: currency, interval: OPTIONS_EXPORT_INTERVAL },
|
|
1596
|
+
// Exports state each decimal's unit in its header and end every row with the spot and dates it is as of.
|
|
1517
1597
|
columns: [
|
|
1598
|
+
{ key: "side", header: "Side" },
|
|
1599
|
+
{ key: "contract", header: "Contract" },
|
|
1600
|
+
{ key: "expiration", header: "Expiry" },
|
|
1601
|
+
{ key: "strike", header: "Strike" },
|
|
1602
|
+
{ key: "last", header: "Last" },
|
|
1603
|
+
{ key: "bid", header: "Bid" },
|
|
1604
|
+
{ key: "ask", header: "Ask" },
|
|
1605
|
+
{ key: "iv", header: "IV (annualized decimal)" },
|
|
1606
|
+
{ key: "delta", header: "Delta (decimal)" },
|
|
1607
|
+
{ key: "volume", header: "Vol" },
|
|
1608
|
+
{ key: "openInterest", header: "OI" },
|
|
1609
|
+
{ key: "spot", header: "Spot" },
|
|
1610
|
+
{ key: "spotBasis", header: "Spot basis" },
|
|
1611
|
+
{ key: "spotAsOf", header: "Spot as of" },
|
|
1612
|
+
{ key: "openInterestAsOf", header: "OI as of" },
|
|
1613
|
+
],
|
|
1614
|
+
textColumns: [
|
|
1518
1615
|
{ key: "side", header: "Side" },
|
|
1519
1616
|
{ key: "contract", header: "Contract", shrink: false },
|
|
1520
1617
|
// The contract symbol already carries the date, so this goes first when the table is narrow.
|
|
@@ -1532,6 +1629,9 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1532
1629
|
});
|
|
1533
1630
|
}
|
|
1534
1631
|
|
|
1632
|
+
/** What an options chain export's `interval` says: one quote snapshot, not bars. */
|
|
1633
|
+
const OPTIONS_EXPORT_INTERVAL = "snapshot";
|
|
1634
|
+
|
|
1535
1635
|
const FX_USAGE = "fx <currency> | fx <base>/<quote>";
|
|
1536
1636
|
|
|
1537
1637
|
async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
@@ -1577,6 +1677,8 @@ async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]
|
|
|
1577
1677
|
})));
|
|
1578
1678
|
ctx.printResult({ data: [row], freshness }, {
|
|
1579
1679
|
text: () => describeFxRate(request, rate),
|
|
1680
|
+
// The rate is the base currency's price of one unit of the other, at the latest quote of each leg.
|
|
1681
|
+
exportIdentity: { symbolOrSeries: pair, currencyOrUnit: baseCurrency, interval: "spot" },
|
|
1580
1682
|
columns: [
|
|
1581
1683
|
{ key: "currency", header: "Currency" },
|
|
1582
1684
|
{ key: "baseCurrency", header: "Base" },
|
|
@@ -1689,7 +1791,14 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
1689
1791
|
title: "Bars",
|
|
1690
1792
|
lines: [
|
|
1691
1793
|
`Each range comes in one bar size: ${historyIntervalsByRange()}. The line under the heading, and the --json metadata, give the currency, the bar size actually served, the first and last bar and the bar count. A warning says when the bars are coarser, or start later, than the range asked for, and for a coin, which trades every day, names each day it has no bar for.`,
|
|
1692
|
-
"A bar whose prices contradict each other (high below the open or close, or low above them) is left blank and flagged, such as high<open, with the count in a warning. Every
|
|
1794
|
+
"A bar whose prices contradict each other (high below the open or close, or low above them) is left blank and flagged, such as high<open, with the count in a warning. Every --csv and --ndjson row carries its flag and ends with symbol_or_series, currency_or_unit, interval, source and as_of; --json rows carry currency, unit, interval and flag.",
|
|
1795
|
+
"A break in the series flags the bar after it, which keeps its prices: gap after more than three months with no bar (weeks for weekly, daily and intraday bars), jump when the close is over 100 times the one before or under a hundredth of it. A warning names each break, and the heading says the date the series runs unbroken from.",
|
|
1796
|
+
],
|
|
1797
|
+
}, {
|
|
1798
|
+
title: "Long history",
|
|
1799
|
+
lines: [
|
|
1800
|
+
"ZAR has daily FRED data from 1980: gloomberb fred DEXSFUS --start 1980 --sort asc --csv",
|
|
1801
|
+
"NGN, SAR and ILS have no FRED series. Their daily bars cover the last five years (gloomberb fn G USDNGN=X --rangePreset 5Y --chartResolution 1d --csv); before that there are monthly bars only (gloomberb history USDNGN=X --range ALL).",
|
|
1693
1802
|
],
|
|
1694
1803
|
}],
|
|
1695
1804
|
examples: ["history AAPL", "history BHP:ASX --range 5Y", "history ZAR=X --range ALL", "history ZAR=X --tail 5", "history AAPL --range 5Y --csv > aapl.csv"],
|
|
@@ -5,6 +5,12 @@ import { cliStyles, cliTerminalWidth, wrapText } from "../../utils/cli-output";
|
|
|
5
5
|
import { isFiniteNumber } from "../../utils/guards";
|
|
6
6
|
import { calculateOptionGreeks, solveChainVolatilities } from "../../plugins/builtin/options/analytics";
|
|
7
7
|
import { optionQuoteSide } from "../../plugins/builtin/options/market-reference";
|
|
8
|
+
import {
|
|
9
|
+
formatModelSpotPrice, modelSpotLabel, optionModelSpot, type OptionModelSpot,
|
|
10
|
+
} from "../../plugins/builtin/options/model-spot";
|
|
11
|
+
import {
|
|
12
|
+
chainOpenInterestAsOf, loadListedOpenInterest, openInterestAsOfLabel, type OpenInterestAsOf,
|
|
13
|
+
} from "../../plugins/builtin/options/open-interest-as-of";
|
|
8
14
|
import { DEFAULT_OPTION_CALC_DRAFT } from "../../plugins/builtin/options-calculator/model";
|
|
9
15
|
import { expiryIsoDate, missingExpiryText, parseOptionExpiration } from "../../utils/option-expiry";
|
|
10
16
|
|
|
@@ -61,6 +67,8 @@ export function formatExpiryList(expirations: readonly number[], width = cliTerm
|
|
|
61
67
|
export interface OptionModelInputs {
|
|
62
68
|
/** The underlying's current price; absent when the quote is missing or stale. */
|
|
63
69
|
spot?: number;
|
|
70
|
+
/** The quote `spot` is read from, for the spot the chain is valued at (`optionModelSpot`). */
|
|
71
|
+
quote?: Quote;
|
|
64
72
|
dividendYield?: number;
|
|
65
73
|
now?: number;
|
|
66
74
|
}
|
|
@@ -119,7 +127,7 @@ export async function loadOptionModelInputs(
|
|
|
119
127
|
.find((candidate) => candidate && candidate.stale !== true && isFiniteNumber(candidate.price) && candidate.price > 0);
|
|
120
128
|
const dividendYield = financials?.fundamentals?.dividendYield;
|
|
121
129
|
return {
|
|
122
|
-
...(current ? { spot: current.price } : {}),
|
|
130
|
+
...(current ? { spot: current.price, quote: current } : {}),
|
|
123
131
|
...(isFiniteNumber(dividendYield) && dividendYield >= 0 ? { dividendYield } : {}),
|
|
124
132
|
};
|
|
125
133
|
}
|
|
@@ -131,6 +139,72 @@ export function chainWithModelFigures(chain: OptionsChain, inputs: OptionModelIn
|
|
|
131
139
|
return { ...chain, calls: chain.calls.map(add), puts: chain.puts.map(add) };
|
|
132
140
|
}
|
|
133
141
|
|
|
142
|
+
/**
|
|
143
|
+
* What a chain's IV, delta and open interest are as of: the spot and instant
|
|
144
|
+
* they are valued at, and the session the open interest counts. The rows and
|
|
145
|
+
* the closing lines of every format say it.
|
|
146
|
+
*/
|
|
147
|
+
export interface OptionsChainBasis {
|
|
148
|
+
spot: OptionModelSpot | null;
|
|
149
|
+
dividendYield: number | null;
|
|
150
|
+
openInterest: OpenInterestAsOf | null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The chain with `iv` and `delta` valued at the spot `optionModelSpot` picks for it, and that spot. */
|
|
154
|
+
export function valueChainAtModelSpot(
|
|
155
|
+
chain: OptionsChain, inputs: OptionModelInputs, now: number, exchange?: string,
|
|
156
|
+
): { chain: OptionsChain; spot: OptionModelSpot | null } {
|
|
157
|
+
const spot = optionModelSpot(inputs.quote, chain, now, exchange);
|
|
158
|
+
return {
|
|
159
|
+
chain: chainWithModelFigures(chain, spot ? { spot: spot.price, dividendYield: inputs.dividendYield, now: spot.valuationTime } : inputs),
|
|
160
|
+
spot,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** The open-interest date of the chain's expiry, from OPX's listing of it; null when the listing cannot be read. */
|
|
165
|
+
export async function loadOptionsChainOpenInterest(
|
|
166
|
+
symbol: string, chain: OptionsChain, listed?: ReturnType<typeof loadListedOpenInterest> | null,
|
|
167
|
+
): Promise<OpenInterestAsOf | null> {
|
|
168
|
+
const expiration = [...chain.calls, ...chain.puts][0]?.expiration;
|
|
169
|
+
if (expiration == null) return null;
|
|
170
|
+
const payload = await (listed ?? loadListedOpenInterest(symbol, expiryIsoDate(expiration)));
|
|
171
|
+
return payload ? chainOpenInterestAsOf(chain, payload) : null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** OPX's listing of an expiry, requested while the chain loads. */
|
|
175
|
+
export function requestListedOpenInterest(symbol: string, expiration: number) {
|
|
176
|
+
return loadListedOpenInterest(symbol, expiryIsoDate(expiration));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The line under the chain: `Greeks at spot 89.79 (close Mon 12 Oct 20:00 UTC), dividend yield 2.34%, rate 4% · OI as of Fri 9 Oct`.
|
|
181
|
+
* An open interest whose date cannot be established says why.
|
|
182
|
+
*/
|
|
183
|
+
export function optionsBasisFootnote(basis: OptionsChainBasis): string {
|
|
184
|
+
const parts: string[] = [];
|
|
185
|
+
if (basis.spot) {
|
|
186
|
+
parts.push(`Greeks at spot ${formatModelSpotPrice(basis.spot.price)} (${modelSpotLabel(basis.spot)}), `
|
|
187
|
+
+ `dividend yield ${basis.dividendYield == null ? "0%" : `${Number((basis.dividendYield * 100).toFixed(2))}%`}, `
|
|
188
|
+
+ `rate ${Number((OPTION_MODEL_RATE * 100).toFixed(2))}%`);
|
|
189
|
+
}
|
|
190
|
+
const oi = basis.openInterest;
|
|
191
|
+
parts.push(oi?.date ? openInterestAsOfLabel(oi)
|
|
192
|
+
: oi?.listedDate && oi.compared > 0
|
|
193
|
+
? `OI date not published: the counts differ from OPX's ${oi.listedDate} counts on ${oi.compared - oi.matched} of ${oi.compared} contracts`
|
|
194
|
+
: openInterestAsOfLabel(null));
|
|
195
|
+
return parts.join(" · ");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** The spot and open-interest fields every exported row carries. */
|
|
199
|
+
export function optionsBasisFields(basis: OptionsChainBasis) {
|
|
200
|
+
return {
|
|
201
|
+
spot: basis.spot?.price ?? null,
|
|
202
|
+
spotBasis: basis.spot ? basis.spot.basis === "close" ? "close" : basis.spot.session ?? "last" : null,
|
|
203
|
+
spotAsOf: basis.spot ? new Date(basis.spot.observedAt).toISOString() : null,
|
|
204
|
+
openInterestAsOf: basis.openInterest?.date ?? null,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
134
208
|
export function optionRows(chain: OptionsChain) {
|
|
135
209
|
const contracts = [
|
|
136
210
|
...chain.calls.map((contract) => ({ side: "call", contract })),
|
|
@@ -40,13 +40,17 @@ import { formatBasisPoints, toBasisPoints } from "../../utils/basis-points";
|
|
|
40
40
|
import {
|
|
41
41
|
basisPointsCell,
|
|
42
42
|
DEFAULT_FRED_START,
|
|
43
|
+
filterFredSeries,
|
|
44
|
+
fredEmptyAdvice,
|
|
43
45
|
fredEmptyMessage,
|
|
44
46
|
fredExportValue,
|
|
45
47
|
fredHistoryNotes,
|
|
48
|
+
fredInterval,
|
|
49
|
+
fredObservationsFromStart,
|
|
46
50
|
fredProvenance,
|
|
47
51
|
fredPublicationTime,
|
|
52
|
+
fredUnit,
|
|
48
53
|
fredValueHeader,
|
|
49
|
-
fredValueKey,
|
|
50
54
|
isSpreadSeries,
|
|
51
55
|
laggingSeriesNote,
|
|
52
56
|
} from "./fred-series";
|
|
@@ -346,13 +350,10 @@ async function runFredList(filter: string, ctx: Parameters<CliCommandDef["execut
|
|
|
346
350
|
if (error instanceof ApiRequestError && error.status === 404) ctx.fail("The list of supported FRED series is not available yet.");
|
|
347
351
|
throw error;
|
|
348
352
|
}
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
const text = `${series.id} ${series.title} ${series.group}`.toLowerCase();
|
|
354
|
-
return terms.every((term) => text.includes(term));
|
|
355
|
-
});
|
|
353
|
+
const rows = filterFredSeries(
|
|
354
|
+
catalog.series.map((series) => ({ id: series.id, title: series.title?.trim() ?? "", group: series.group?.trim() ?? "" })),
|
|
355
|
+
filter,
|
|
356
|
+
);
|
|
356
357
|
ctx.printResult({ data: rows, metadata: { filter: filter || null, total: catalog.series.length } }, {
|
|
357
358
|
columns: [
|
|
358
359
|
{ key: "id", header: "ID", shrink: false },
|
|
@@ -381,15 +382,17 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
381
382
|
if (isUnsupportedSeries(error)) ctx.fail(`Unsupported FRED series ${seriesId}. List supported series with: gloomberb fred --list`);
|
|
382
383
|
throw error;
|
|
383
384
|
});
|
|
384
|
-
//
|
|
385
|
-
const
|
|
385
|
+
// Every observation exports as `value`, a spread in basis points; its unit goes in the `currency_or_unit` column.
|
|
386
|
+
const unit = fredUnit(seriesId, data.info);
|
|
387
|
+
const interval = fredInterval(data.info?.frequency);
|
|
386
388
|
// A value FRED does not list yet carries its publisher and when it published, in every export.
|
|
387
389
|
const exportRow = (observation: { date: string; value: number | null; publisher?: string; publishedAt?: string }) => ({
|
|
388
390
|
date: observation.date,
|
|
389
|
-
|
|
391
|
+
value: fredExportValue(seriesId, observation.value),
|
|
390
392
|
...(observation.publisher?.trim() ? { publisher: observation.publisher.trim(), ...(observation.publishedAt ? { publishedAt: observation.publishedAt } : {}) } : {}),
|
|
391
393
|
});
|
|
392
|
-
|
|
394
|
+
// A --start before the series begins comes back padded with blank days: the rows start at its first observation.
|
|
395
|
+
const rows = fredObservationsFromStart(data.observations, data.info?.observationStart).map(exportRow);
|
|
393
396
|
// The default start hides older history and a start before the series begins is moved up silently: say where it really starts.
|
|
394
397
|
const notes = [
|
|
395
398
|
...fredHistoryNotes({ startDate, startGiven: start != null, seriesStart: data.info?.observationStart }),
|
|
@@ -398,6 +401,10 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
398
401
|
// Nothing on or after the start: name where the series ends, so an empty answer is not a dead end.
|
|
399
402
|
const latest = rows.length > 0 ? null : await apiClient.getCloudFredSeries(seriesId, { limit: 1, sortOrder: "desc" })
|
|
400
403
|
.then((payload) => payload.observations[0] ?? null).catch(() => null);
|
|
404
|
+
// CSV and NDJSON are rows alone: an empty one would read as a series with no data, so it fails and says where the series ends.
|
|
405
|
+
if (rows.length === 0 && (ctx.cliOptions.format === "csv" || ctx.cliOptions.format === "ndjson")) {
|
|
406
|
+
ctx.fail(`No ${seriesId} observations on or after ${startDate}.`, fredEmptyAdvice(seriesId, latest));
|
|
407
|
+
}
|
|
401
408
|
const periodicity = periodicityOf(data.info?.frequency);
|
|
402
409
|
const newestDate = rows.reduce<string | null>((newest, row) => (!newest || row.date > newest ? row.date : newest), null);
|
|
403
410
|
// A newest value FRED does not list yet names its publisher, Treasury for a yield; FRED's own stay "FRED".
|
|
@@ -408,7 +415,7 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
408
415
|
ctx.printResult({
|
|
409
416
|
data: rows,
|
|
410
417
|
metadata: {
|
|
411
|
-
info: data.info, seriesId, startDate, sortOrder,
|
|
418
|
+
info: data.info, seriesId, startDate, sortOrder, unit, interval,
|
|
412
419
|
...(notes.length ? { notes } : {}),
|
|
413
420
|
...(latest ? { latestObservation: exportRow(latest) } : {}),
|
|
414
421
|
},
|
|
@@ -422,12 +429,13 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
422
429
|
}, {
|
|
423
430
|
dateKey: "date",
|
|
424
431
|
empty: fredEmptyMessage(startDate, latest),
|
|
425
|
-
...(notes.length ? { footnote: notes.join("\n") } : {}),
|
|
432
|
+
...(notes.length ? { footnote: notes.join("\n"), exportNotes: notes } : {}),
|
|
433
|
+
exportIdentity: { symbolOrSeries: seriesId, currencyOrUnit: unit, interval },
|
|
426
434
|
textColumns: [
|
|
427
435
|
{ key: "date", header: "Date" },
|
|
428
436
|
isSpreadSeries(seriesId)
|
|
429
|
-
? { key:
|
|
430
|
-
: { key:
|
|
437
|
+
? { key: "value", header: "Value (bp)", align: "right", format: basisPointsCell }
|
|
438
|
+
: { key: "value", header: fredValueHeader(data.info), align: "right" },
|
|
431
439
|
],
|
|
432
440
|
});
|
|
433
441
|
}
|
|
@@ -445,12 +453,12 @@ async function runYieldCurve(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
445
453
|
return {
|
|
446
454
|
seriesId,
|
|
447
455
|
date: latest?.date ?? "",
|
|
448
|
-
// Yields in percent,
|
|
449
|
-
|
|
456
|
+
// Yields in percent, which the exports' `currency_or_unit` column says (`pct`), as `fred` does.
|
|
457
|
+
value: latest?.value ?? null,
|
|
450
458
|
title: data.info?.title ?? "",
|
|
451
459
|
// The move since the previous observation FRED has, not calendar yesterday: a Monday reads against Friday.
|
|
452
460
|
previousDate: previous?.date ?? null,
|
|
453
|
-
|
|
461
|
+
previousValue: previous?.value ?? null,
|
|
454
462
|
changeBasisPoints: latest?.value != null && previous?.value != null ? toBasisPoints(latest.value - previous.value, 1) : null,
|
|
455
463
|
latest,
|
|
456
464
|
publishedAt: latest?.publisher ? null : fredPublicationTime(data.info, latest?.date),
|
|
@@ -470,9 +478,10 @@ async function runYieldCurve(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
470
478
|
...(publishedAt ? { publishedAt } : {}),
|
|
471
479
|
}),
|
|
472
480
|
}, {
|
|
481
|
+
exportIdentity: (row) => ({ symbolOrSeries: String(row.seriesId), currencyOrUnit: "pct", interval: "daily" }),
|
|
473
482
|
textColumns: [
|
|
474
483
|
{ key: "tenor", header: "Tenor", value: (row) => YIELD_TENORS[String(row.seriesId)] ?? row.seriesId },
|
|
475
|
-
{ key: "
|
|
484
|
+
{ key: "value", header: "Yield %", align: "right", format: (value) => typeof value === "number" ? value.toFixed(2) : "-" },
|
|
476
485
|
{
|
|
477
486
|
key: "changeBasisPoints",
|
|
478
487
|
header: "1D bp",
|