gloomberb 0.19.0 → 0.19.2
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 +2 -1
- package/patches/electrobun@1.18.1.patch +23 -1
- package/src/api-client/app-attention.ts +3 -0
- package/src/api-client/beneficial-owners.ts +3 -0
- package/src/api-client/company-kpis.ts +3 -0
- package/src/api-client/data.ts +2 -1
- package/src/api-client/errors.ts +38 -0
- package/src/api-client/hiring.ts +3 -0
- package/src/api-client/index.ts +4 -0
- package/src/api-client/ipo.ts +24 -1
- package/src/api-client/mna.ts +3 -0
- package/src/api-client/paths.ts +9 -1
- package/src/api-client/roadmap.ts +85 -0
- package/src/api-client/types.ts +12 -0
- package/src/app/runtime/startup.ts +3 -1
- package/src/architecture/core-builtin-allowlist.json +2 -2
- package/src/capabilities/chart-series.ts +2 -1
- package/src/cli/commands/market.ts +44 -16
- package/src/cli/commands/news-search.ts +22 -0
- package/src/cli/commands/overview.ts +84 -19
- package/src/cli/commands/search.ts +13 -13
- package/src/cli/commands/ticker.ts +32 -2
- package/src/cli/context.ts +1 -0
- package/src/cli/errors.ts +17 -2
- package/src/cli/glossary.ts +8 -1
- package/src/cli/history-rows.ts +35 -7
- package/src/cli/index-roots.ts +0 -7
- package/src/cli/known-gaps.ts +11 -0
- package/src/cli/listing-arg.ts +74 -11
- package/src/cli/pane-functions/capabilities.ts +1 -0
- package/src/cli/pane-functions/discovery.ts +21 -1
- package/src/cli/pane-functions/freshness.ts +7 -1
- package/src/cli/pane-functions/function-help.ts +7 -0
- package/src/cli/pane-functions/index.ts +6 -3
- package/src/cli/quote-answer.ts +30 -7
- package/src/cli/saved-copy-persistence.ts +88 -0
- package/src/components/command-bar/list/model.ts +5 -0
- package/src/components/command-bar/routes/root/runtime.ts +29 -8
- package/src/components/command-bar/selection-runtime.ts +64 -32
- package/src/components/command-bar/surface/index.tsx +2 -0
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/header.tsx +2 -2
- package/src/components/layout/pane/capture-stamp.ts +2 -2
- package/src/components/layout/pane/footer/index.tsx +23 -6
- package/src/components/layout/pane/footer/model.ts +116 -16
- package/src/components/layout/pane/header.tsx +2 -2
- package/src/components/layout/pane/status-footer.ts +37 -3
- package/src/components/layout/status-widget-space.ts +9 -0
- package/src/components/onboarding/onboarding-frame.tsx +2 -1
- package/src/components/onboarding/onboarding-wizard.tsx +6 -3
- package/src/components/ticker/list-table-view.tsx +4 -20
- package/src/components/ui/display.tsx +29 -3
- package/src/components/ui/icon.tsx +12 -3
- package/src/core/app-services.ts +11 -2
- package/src/data/app-persistence.ts +9 -7
- package/src/data/plugin-cache.ts +20 -3
- package/src/data/plugin-state-store.ts +81 -24
- package/src/data/resource-store.ts +130 -58
- package/src/data/saved-copies.ts +8 -0
- package/src/data/session-store.ts +31 -8
- package/src/data/sqlite/access.ts +313 -0
- package/src/data/sqlite/checkpointer.ts +198 -0
- package/src/data/sqlite/database.ts +47 -4
- package/src/data/sqlite/retry.ts +50 -9
- package/src/data/ticker-store.ts +57 -23
- package/src/docs/snapshot.json +32 -5
- package/src/i18n/es.ts +2 -4
- package/src/i18n/ja.ts +2 -4
- package/src/i18n/ko.ts +2 -4
- package/src/i18n/zh-cn.ts +2 -4
- package/src/i18n/zh-tw.ts +2 -4
- package/src/market-data/depositary-pair.ts +25 -1
- package/src/market-data/quotes/metadata.ts +7 -0
- package/src/market-data/realized-volatility.ts +4 -2
- package/src/news/aggregator.ts +7 -1
- package/src/news/types.ts +6 -0
- package/src/plugins/auto-update-node.ts +10 -6
- package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
- package/src/plugins/builtin/alerts/cli.ts +34 -11
- package/src/plugins/builtin/alerts/event-rows.ts +24 -3
- package/src/plugins/builtin/alerts/events-pane.tsx +15 -6
- package/src/plugins/builtin/alerts/headless.ts +10 -6
- package/src/plugins/builtin/cds/client.ts +142 -30
- package/src/plugins/builtin/cds/headless.ts +35 -8
- package/src/plugins/builtin/cds/model.ts +145 -18
- package/src/plugins/builtin/cds/pane.tsx +16 -5
- package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
- package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +5 -0
- package/src/plugins/builtin/chart-composer/index.tsx +4 -5
- package/src/plugins/builtin/chart-composer/pane.tsx +5 -1
- package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
- package/src/plugins/builtin/chat/controller/index.ts +5 -1
- package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
- package/src/plugins/builtin/chat/controller/state.ts +25 -2
- package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
- package/src/plugins/builtin/chat/controller/utils.ts +7 -2
- package/src/plugins/builtin/chat/status-preview.ts +79 -0
- package/src/plugins/builtin/chat/status-widget.tsx +147 -10
- package/src/plugins/builtin/chat/unread-badge.ts +51 -10
- package/src/plugins/builtin/clinical-trials/client.ts +49 -4
- package/src/plugins/builtin/clinical-trials/model.ts +86 -22
- package/src/plugins/builtin/clinical-trials/pane.tsx +46 -27
- package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +87 -9
- package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
- package/src/plugins/builtin/commodity-balances/client.ts +4 -1
- package/src/plugins/builtin/company-attention/headless.ts +2 -1
- package/src/plugins/builtin/company-attention/pane.tsx +3 -0
- package/src/plugins/builtin/company-kpis/headless.ts +8 -3
- package/src/plugins/builtin/company-kpis/model.ts +73 -5
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/composite-plugins.ts +2 -1
- package/src/plugins/builtin/cot/client.ts +28 -2
- package/src/plugins/builtin/cot/headless.ts +4 -1
- package/src/plugins/builtin/cot/index.tsx +4 -2
- package/src/plugins/builtin/cot/model.ts +58 -7
- package/src/plugins/builtin/cot/pane.tsx +6 -2
- package/src/plugins/builtin/cpi/client.ts +6 -5
- package/src/plugins/builtin/credit-boards/series.ts +3 -4
- package/src/plugins/builtin/credit-conditions/client.ts +22 -14
- package/src/plugins/builtin/credit-documents/headless.ts +3 -3
- package/src/plugins/builtin/crypto-board/client.ts +5 -5
- package/src/plugins/builtin/crypto-board/series.ts +4 -4
- package/src/plugins/builtin/debt-maturities/series.ts +3 -3
- package/src/plugins/builtin/doe/client.ts +6 -5
- package/src/plugins/builtin/doe/headless.ts +3 -2
- package/src/plugins/builtin/doe/pane.tsx +13 -3
- package/src/plugins/builtin/doe/prices.ts +2 -3
- package/src/plugins/builtin/doe/series.ts +4 -4
- package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
- package/src/plugins/builtin/earnings-calls/data.ts +37 -17
- package/src/plugins/builtin/earnings-calls/headless.ts +13 -3
- package/src/plugins/builtin/earnings-calls/pane.tsx +34 -14
- package/src/plugins/builtin/econ/calendar-model.ts +48 -1
- package/src/plugins/builtin/econ/calendar-source.ts +4 -2
- package/src/plugins/builtin/econ/detail-view.tsx +18 -4
- package/src/plugins/builtin/econ/figures.ts +48 -0
- package/src/plugins/builtin/econ/index.tsx +14 -5
- package/src/plugins/builtin/econ/print-watch.ts +3 -1
- package/src/plugins/builtin/econ-statistics/client.ts +11 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +10 -2
- package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +3 -0
- package/src/plugins/builtin/executives/data.ts +6 -1
- package/src/plugins/builtin/executives/pane.tsx +9 -21
- package/src/plugins/builtin/fear-greed/cache.ts +4 -0
- package/src/plugins/builtin/fear-greed/series.ts +6 -4
- package/src/plugins/builtin/gpu/client.ts +4 -1
- package/src/plugins/builtin/gpu/series.ts +2 -3
- package/src/plugins/builtin/help/index.tsx +61 -59
- package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
- package/src/plugins/builtin/help/table-cells.tsx +13 -2
- package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +4 -2
- package/src/plugins/builtin/holders/headless.ts +4 -2
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/holders/report-rows.ts +4 -1
- package/src/plugins/builtin/insider/headless.ts +2 -2
- package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
- package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
- package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
- package/src/plugins/builtin/jodi/client.ts +4 -1
- package/src/plugins/builtin/macro-day/headless.ts +12 -5
- package/src/plugins/builtin/macro-day/index.tsx +1 -1
- package/src/plugins/builtin/macro-day/model.ts +39 -5
- package/src/plugins/builtin/macro-day/pane.tsx +27 -19
- package/src/plugins/builtin/market-movers/client.ts +3 -0
- package/src/plugins/builtin/market-movers/headless.ts +3 -1
- package/src/plugins/builtin/market-movers/index.tsx +10 -5
- package/src/plugins/builtin/market-movers/screener.ts +27 -6
- package/src/plugins/builtin/market-movers/session-body.tsx +2 -1
- package/src/plugins/builtin/mna/headless.ts +2 -0
- package/src/plugins/builtin/mna/pane.tsx +3 -1
- package/src/plugins/builtin/money-markets/series.ts +3 -5
- package/src/plugins/builtin/news/headless.ts +3 -1
- package/src/plugins/builtin/news/index.tsx +3 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +2 -6
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +2 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +4 -6
- package/src/plugins/builtin/power/gas-client.ts +2 -1
- package/src/plugins/builtin/power/prices-client.ts +3 -2
- package/src/plugins/builtin/realized-vol/client.ts +13 -5
- package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
- package/src/plugins/builtin/realized-vol/headless.ts +8 -4
- package/src/plugins/builtin/realized-vol/model.ts +37 -6
- package/src/plugins/builtin/realized-vol/pane.tsx +9 -7
- package/src/plugins/builtin/research/event-model.ts +30 -4
- package/src/plugins/builtin/research/index.tsx +7 -3
- package/src/plugins/builtin/research/relative-valuation-headless.ts +8 -25
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +52 -15
- package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
- package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
- package/src/plugins/builtin/risk-factors/data.ts +12 -6
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/risk-factors/index.tsx +3 -1
- package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
- package/src/plugins/builtin/roadmap/index.tsx +28 -0
- package/src/plugins/builtin/roadmap/model.ts +67 -0
- package/src/plugins/builtin/roadmap/pane.tsx +244 -0
- package/src/plugins/builtin/scanner/live-headless.ts +21 -2
- package/src/plugins/builtin/scanner/lock-note.ts +26 -3
- package/src/plugins/builtin/sec/headless.ts +2 -1
- package/src/plugins/builtin/sec/index.tsx +3 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +20 -7
- package/src/plugins/builtin/shared/fred-points.ts +6 -1
- package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
- package/src/plugins/builtin/shared/report-freshness.ts +8 -0
- package/src/plugins/builtin/shared/research-cells.tsx +1 -1
- package/src/plugins/builtin/shared/series-cache.ts +4 -4
- package/src/plugins/builtin/shared/series-warning.ts +13 -4
- package/src/plugins/builtin/short-volume/series.ts +2 -3
- package/src/plugins/builtin/social-mentions/client.ts +15 -10
- package/src/plugins/builtin/social-mentions/series.ts +2 -3
- package/src/plugins/builtin/thirteenf/api.ts +15 -9
- package/src/plugins/builtin/thirteenf/data.ts +39 -11
- package/src/plugins/builtin/thirteenf/headless.ts +31 -4
- package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +21 -5
- package/src/plugins/builtin/thirteenf/types.ts +2 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +5 -2
- package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
- package/src/plugins/builtin/ticker-detail/headless.ts +59 -8
- package/src/plugins/builtin/ticker-detail/overview/model.ts +10 -2
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +11 -6
- package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
- package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
- package/src/plugins/builtin/treasury-auctions/headless.ts +24 -13
- package/src/plugins/builtin/treasury-auctions/pane.tsx +18 -6
- package/src/plugins/builtin/volatility/client.ts +43 -16
- package/src/plugins/builtin/volatility/headless.ts +5 -2
- package/src/plugins/builtin/volatility/model.ts +17 -6
- package/src/plugins/builtin/volatility/pane.tsx +11 -4
- package/src/plugins/builtin/volatility/series.ts +2 -1
- package/src/plugins/builtin/yield-curve/client.ts +59 -1
- package/src/plugins/builtin/yield-curve/headless.ts +9 -6
- package/src/plugins/builtin/yield-curve/index.tsx +34 -10
- package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/host-link.ts +1 -1
- package/src/plugins/installer.ts +50 -39
- package/src/portfolio/column-values.ts +11 -7
- package/src/renderers/dom/desktop/icons.tsx +10 -2
- package/src/renderers/electrobun/bun/desktop/backend.ts +9 -10
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +8 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +9 -1
- package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
- package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
- package/src/renderers/electrobun/bun/index.ts +26 -2
- package/src/renderers/electrobun/bun/window/attention.ts +54 -0
- package/src/renderers/electrobun/bun/window/create.ts +45 -4
- package/src/renderers/electrobun/bun/window/events.ts +20 -0
- package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
- package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
- package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
- package/src/renderers/electrobun/launcher/preload.ts +13 -0
- package/src/renderers/electrobun/launcher/windows-emulation.ts +50 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/news-source.ts +2 -0
- package/src/renderers/electrobun/view/ui-host.tsx +16 -1
- package/src/series/resolve.ts +2 -1
- package/src/series/types.ts +5 -0
- package/src/sources/gloomberb-cloud/fred-series.ts +8 -4
- package/src/sources/gloomberb-cloud/index.ts +42 -8
- package/src/telemetry/stall-reports-node.ts +13 -2
- package/src/telemetry/stall-reports.ts +189 -13
- package/src/test-support/outage.ts +8 -0
- package/src/tickers/company-name-listing.ts +110 -40
- package/src/tickers/company-name.ts +71 -2
- package/src/tickers/index-roots.ts +7 -0
- package/src/tickers/listing-fate.ts +2 -2
- package/src/tickers/search/home-listing.ts +93 -8
- package/src/tickers/search/index.ts +7 -3
- package/src/tickers/search/ranking.ts +5 -1
- package/src/tickers/search/upsert.ts +13 -0
- package/src/tickers/ticker-changes.ts +8 -4
- package/src/time-series/market.ts +5 -3
- package/src/time-series/price-comparison.ts +20 -2
- package/src/time-series/resolve.ts +20 -1
- package/src/time-series/types.ts +7 -0
- package/src/types/company-from.ts +40 -0
- package/src/types/data-provider.ts +7 -0
- package/src/types/financials.ts +45 -3
- package/src/types/headless.ts +6 -0
- package/src/ui/host.tsx +6 -1
- package/src/utils/crypto-pair.ts +121 -7
- package/src/utils/declared-dividends.ts +1 -1
- package/src/utils/financial-statements.ts +73 -16
- package/src/utils/fundamentals.ts +53 -10
- package/src/utils/lease-liabilities.ts +54 -0
- package/src/utils/main-thread-stall.ts +47 -2
- package/src/utils/perf-marks.ts +23 -11
- package/src/utils/price-history-integrity.ts +47 -1
- package/src/version.ts +2 -2
|
@@ -3,6 +3,7 @@ import type { CliCommandDef } from "../../types/plugin";
|
|
|
3
3
|
import { TIME_RANGES, type TimeRange } from "../../time-series/range";
|
|
4
4
|
import { yearToDateStartIndex } from "../../market-data/market/year-to-date";
|
|
5
5
|
import type { EarningsEvent, QuoteBatchResult, QuoteSubscriptionTarget, SecFilingItem } from "../../types/data-provider";
|
|
6
|
+
import { companyDataNotes, type CompanyFrom } from "../../types/company-from";
|
|
6
7
|
import type { NewsArticle, NewsFeed, NewsQuery } from "../../news/types";
|
|
7
8
|
import type {
|
|
8
9
|
AnalystResearchData,
|
|
@@ -39,7 +40,7 @@ import {
|
|
|
39
40
|
quoteChangeText,
|
|
40
41
|
} from "../helpers";
|
|
41
42
|
import { cliArrow, cliStyles, colorBySign, renderSection, renderStats } from "../../utils/cli-output";
|
|
42
|
-
import { filledPeriodsNote, stalePeriodNote, statementHistoryNotes } from "../../utils/financial-statements";
|
|
43
|
+
import { filledPeriodsNote, hasOlderStatements, stalePeriodNote, statementHistoryNotes } from "../../utils/financial-statements";
|
|
43
44
|
import { formatPerShareNumber } from "../../utils/reported-money";
|
|
44
45
|
import { isCurrencyPairListing } from "../../utils/fx-market-hours";
|
|
45
46
|
import {
|
|
@@ -130,6 +131,7 @@ import {
|
|
|
130
131
|
historyCoverageWarning,
|
|
131
132
|
historyFacts,
|
|
132
133
|
historyFlagNote,
|
|
134
|
+
historyWickNote,
|
|
133
135
|
historyGapNote,
|
|
134
136
|
historyJumpNote,
|
|
135
137
|
historyUnbrokenFrom,
|
|
@@ -165,6 +167,7 @@ import { bareFuturesCode, futuresBoardContract } from "../../market-data/futures
|
|
|
165
167
|
import { commodityNamedIn, commoditySearchName } from "../../market-data/commodity-names";
|
|
166
168
|
import { newsEmptyMessage } from "./news-hints";
|
|
167
169
|
import {
|
|
170
|
+
closestStoriesReason,
|
|
168
171
|
commodityNewsRoute,
|
|
169
172
|
commonWordCandidate,
|
|
170
173
|
METALS_NEWS_TOPIC,
|
|
@@ -179,6 +182,7 @@ import {
|
|
|
179
182
|
noMetalStoriesMessage,
|
|
180
183
|
noStoriesMessage,
|
|
181
184
|
quotedSearchCommand,
|
|
185
|
+
searchClosestNewsStories,
|
|
182
186
|
searchNewsStories,
|
|
183
187
|
wordMeansMore,
|
|
184
188
|
} from "./news-search";
|
|
@@ -189,7 +193,7 @@ import { exportedFundamentals } from "../../utils/price-earnings";
|
|
|
189
193
|
import { fundamentalsFreshness, fxPairFreshness, quotesFreshness, rowsFreshness, valuationFreshness } from "../freshness";
|
|
190
194
|
import { fxRateNotes } from "../../plugins/builtin/shared/fx-rate-freshness";
|
|
191
195
|
import { quoteBarNotes } from "../../market-data/quotes/intraday-bar";
|
|
192
|
-
import { depositaryPairText, isReceiptSide, listingDepositaryPair } from "../../market-data/depositary-pair";
|
|
196
|
+
import { cedearRatioText, depositaryPairText, isReceiptSide, listingCedearRatio, listingDepositaryPair } from "../../market-data/depositary-pair";
|
|
193
197
|
import { isPesoDollarPair, loadPesoDollarRates, pesoDollarRateLines, pesoDollarRatesJson } from "../peso-dollar-rates";
|
|
194
198
|
import { atRegularSessionPrice, regularSessionValuationPrice } from "../../market-data/quotes/regular-session-valuation";
|
|
195
199
|
import { viewMarketCapitalization } from "../../utils/market-capitalization";
|
|
@@ -199,6 +203,7 @@ import {
|
|
|
199
203
|
cloudNewsFreshness,
|
|
200
204
|
cloudRealtimeAccess,
|
|
201
205
|
REPORTED_DATA,
|
|
206
|
+
SEC_FILING_ROWS,
|
|
202
207
|
SEC_FILINGS,
|
|
203
208
|
} from "../../plugins/builtin/shared/report-freshness";
|
|
204
209
|
import {
|
|
@@ -242,7 +247,7 @@ import {
|
|
|
242
247
|
type ListingIdentity,
|
|
243
248
|
} from "../listing-arg";
|
|
244
249
|
import { bareListingNote, listingFateNotes, noLongerTradingNote, tradingListing } from "../../tickers/listing-fate";
|
|
245
|
-
import { isYieldIndexSymbol } from "
|
|
250
|
+
import { isYieldIndexSymbol } from "../../tickers/index-roots";
|
|
246
251
|
import {
|
|
247
252
|
analyzeTaxEquivalent, distributionBasis, loadTreasuryTenors, parseTaxRates, TAX_RATE_HELP_OPTIONS, taxEquivalentJson, type TaxRates,
|
|
248
253
|
} from "../../market-data/tax-equivalent";
|
|
@@ -913,7 +918,7 @@ function corporateActionRows(data: CorporateActionsData) {
|
|
|
913
918
|
...declaredDividends(data).map((event) => ({
|
|
914
919
|
type: "dividend",
|
|
915
920
|
date: event.exDate,
|
|
916
|
-
detail: `declared ${currency ? formatDistributionAmount(event.amount, currency) : cleanDecimal(event.amount)}`
|
|
921
|
+
detail: `declared ${event.special ? "special " : ""}${currency ? formatDistributionAmount(event.amount, currency) : cleanDecimal(event.amount)}`
|
|
917
922
|
+ (event.payDate ? `, payable ${event.payDate}` : ""),
|
|
918
923
|
})),
|
|
919
924
|
...data.splits.map((event) => ({
|
|
@@ -1105,8 +1110,10 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
|
|
|
1105
1110
|
const quiet = ended ? null : noPrintsTodayText(row.quote, now);
|
|
1106
1111
|
return [ended ?? (quiet ? `${label}: ${quiet}` : []), quoteBarNotes(label, row.quote, now)].flat();
|
|
1107
1112
|
}),
|
|
1108
|
-
// A receipt and its home line each say the ratio, as `ticker` does.
|
|
1113
|
+
// A receipt and its home line each say the ratio, as `ticker` does; a CEDEAR names itself in its own.
|
|
1109
1114
|
...data.flatMap((row) => {
|
|
1115
|
+
const cedear = listingCedearRatio({ quote: row.quote });
|
|
1116
|
+
if (cedear) return [`${cedearRatioText(cedear)}.`];
|
|
1110
1117
|
const pair = listingDepositaryPair({ quote: row.quote });
|
|
1111
1118
|
return pair ? [`${row.target.symbol}: ${depositaryPairText(pair, isReceiptSide({ quote: row.quote }))}.`] : [];
|
|
1112
1119
|
}),
|
|
@@ -1458,7 +1465,7 @@ async function loadListingHistory(
|
|
|
1458
1465
|
...window ? [...windowHistoryNotes(window, data), coverage ?? []]
|
|
1459
1466
|
: range === "YTD" ? ytdHistoryNotes(data) : [...historyNotes(range, interval, data[0]?.date, Date.now(), coverage != null), coverage ?? []],
|
|
1460
1467
|
// The status line says how far a bar still forming runs; the note is for the bars it does not name.
|
|
1461
|
-
gaps ?? [], historyJumpNote(data) ?? [], historyFlagNote(data) ?? [], historyMissingVolumeNote(data) ?? [],
|
|
1468
|
+
gaps ?? [], historyJumpNote(data) ?? [], historyFlagNote(data) ?? [], historyWickNote(data) ?? [], historyMissingVolumeNote(data) ?? [],
|
|
1462
1469
|
historyVenueVolumeNote(data) ?? [],
|
|
1463
1470
|
historyBarStatusNote(forming ? served : data, { tradingDay: venueTradingDay(venue, Date.now()) }) ?? [],
|
|
1464
1471
|
].flat();
|
|
@@ -1875,7 +1882,11 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
1875
1882
|
// Years filled from the listing's exchange, for the revenue and net income columns.
|
|
1876
1883
|
const filled = filledPeriodsNote(financials.annualStatements.filter((statement) => rows.some((row) => row.date === statement.date)),
|
|
1877
1884
|
new Set(["totalRevenue", "netIncome"]));
|
|
1878
|
-
|
|
1885
|
+
// GF is pointed to only when the listing's extended history goes further back than these years.
|
|
1886
|
+
const dates = rows.map((row) => row.date);
|
|
1887
|
+
const older = await hasOlderStatements(() => market.dataProvider.getTickerFinancials(listing.request.symbol, listing.request.exchange,
|
|
1888
|
+
{ statementHistory: "extended" }), "annual", dates);
|
|
1889
|
+
const historyNotes = [...stale ? [stale] : [], ...filled ? [filled] : [], ...statementHistoryNotes("annual", dates, listing.key, older)];
|
|
1879
1890
|
// NDJSON keeps the raw statement rows it always wrote; CSV gets the table and its closing lines.
|
|
1880
1891
|
const tables = tabular
|
|
1881
1892
|
? selectTables({
|
|
@@ -1932,26 +1943,29 @@ const NEWS_COLUMNS = [
|
|
|
1932
1943
|
async function printNewsSearch(
|
|
1933
1944
|
text: string,
|
|
1934
1945
|
articles: NewsArticle[],
|
|
1935
|
-
{ reason, empty, commodity = true, failure, ticker = null }: {
|
|
1946
|
+
{ reason, empty, commodity = true, failure, ticker = null, closest = false }: {
|
|
1936
1947
|
reason?: string;
|
|
1937
1948
|
empty?: string;
|
|
1938
1949
|
commodity?: boolean;
|
|
1939
1950
|
failure?: string;
|
|
1940
1951
|
/** The listing a coin pair's search stands in for. */
|
|
1941
1952
|
ticker?: string | null;
|
|
1953
|
+
/** No story has every word: these are the ones with the most. */
|
|
1954
|
+
closest?: boolean;
|
|
1942
1955
|
},
|
|
1943
1956
|
ctx: Parameters<CliCommandDef["execute"]>[1],
|
|
1944
1957
|
) {
|
|
1945
1958
|
const searched = failure === undefined;
|
|
1946
1959
|
const named = searched && commodity && articles.length === 0 && commodityNamedIn(text) !== null;
|
|
1947
1960
|
const route = named ? await commodityNewsRoute(text) : null;
|
|
1961
|
+
const closestReason = closest && articles.length > 0 ? closestStoriesReason(text) : null;
|
|
1948
1962
|
ctx.printResult({
|
|
1949
1963
|
data: articles,
|
|
1950
1964
|
...(searched ? {} : { warnings: [`Searching stories for "${text}" failed: ${failure}`] }),
|
|
1951
|
-
metadata: { ticker, search: text, feed: null, ...(route ? { futuresNews: route.command } : {}) },
|
|
1965
|
+
metadata: { ticker, search: text, feed: null, ...(closestReason ? { match: "closest" } : {}), ...(route ? { futuresNews: route.command } : {}) },
|
|
1952
1966
|
freshness: rowsFreshness(newsRows(articles), cloudNewsFreshness(await cloudRealtimeAccess())),
|
|
1953
1967
|
}, {
|
|
1954
|
-
heading: newsSearchHeading(text, reason),
|
|
1968
|
+
heading: newsSearchHeading(text, [reason, closestReason].filter(Boolean).join(" · ") || undefined),
|
|
1955
1969
|
rows: newsRows,
|
|
1956
1970
|
columns: NEWS_COLUMNS,
|
|
1957
1971
|
empty: named ? noStoriesMessage(text, route) : empty ?? (searched ? noStoriesMessage(text, null) : "No stories to show."),
|
|
@@ -2013,7 +2027,8 @@ async function runNews(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
2013
2027
|
if (exchangeOption || feed) ctx.fail(`${exchangeOption ? "--exchange" : "--feed"} goes with a symbol, not with text to search stories for.`, "Usage: gloom news <query>");
|
|
2014
2028
|
const problem = newsSearchTextProblem(text);
|
|
2015
2029
|
if (problem) ctx.fail(problem);
|
|
2016
|
-
|
|
2030
|
+
const answer = await searchClosestNewsStories(text, limit);
|
|
2031
|
+
await printNewsSearch(text, answer.found, { closest: answer.closest }, ctx);
|
|
2017
2032
|
return;
|
|
2018
2033
|
}
|
|
2019
2034
|
// A metal's future or name is its market's news, not a company's: GOLD in capitals stays Gold.com's.
|
|
@@ -2193,7 +2208,7 @@ async function runFilings(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
2193
2208
|
...listingMetadata(resolved), ...(form ? { form } : {}),
|
|
2194
2209
|
...(page ? { announcementsPage: page.url } : {}),
|
|
2195
2210
|
},
|
|
2196
|
-
freshness: rowsFreshness(filingRows(filings),
|
|
2211
|
+
freshness: rowsFreshness(filingRows(filings), SEC_FILING_ROWS),
|
|
2197
2212
|
...(choice.length > 0 ? { warnings: choice } : {}),
|
|
2198
2213
|
}, {
|
|
2199
2214
|
heading: listingHeading(resolved),
|
|
@@ -2205,7 +2220,7 @@ async function runFilings(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
2205
2220
|
empty: [form ? `No ${form} filings found for ${listingTitle(resolved)}.`
|
|
2206
2221
|
: identity ? noSecReportsNotice(resolved)
|
|
2207
2222
|
: `No SEC filings found for ${listingTitle(resolved)}.`, pageLine].filter(Boolean).join("\n"),
|
|
2208
|
-
...(pageLine
|
|
2223
|
+
...filingsFootnote(pageLine, filings[0]?.companyFrom),
|
|
2209
2224
|
});
|
|
2210
2225
|
} catch (error) {
|
|
2211
2226
|
if (!(error instanceof SecRegistrantMismatchError)) throw error;
|
|
@@ -2223,6 +2238,16 @@ async function runFilings(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
2223
2238
|
});
|
|
2224
2239
|
}
|
|
2225
2240
|
|
|
2241
|
+
/**
|
|
2242
|
+
* The lines `filings` prints under its rows and CSV writes as notes: an ASX
|
|
2243
|
+
* listing's announcements page, and whose filings a foreign line shows when
|
|
2244
|
+
* they are its company's home line's.
|
|
2245
|
+
*/
|
|
2246
|
+
function filingsFootnote(pageLine: string | null | undefined, companyFrom: CompanyFrom | undefined): { footnote?: string; exportNotes?: string[] } {
|
|
2247
|
+
const lines = [...(pageLine ? [pageLine] : []), ...companyDataNotes(companyFrom)];
|
|
2248
|
+
return lines.length ? { footnote: lines.join("\n"), exportNotes: lines } : {};
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2226
2251
|
/**
|
|
2227
2252
|
* `filings` for a company on ASX that files nothing with the SEC: its latest
|
|
2228
2253
|
* ASX announcements from Gloom Cloud, newest first, under the line saying it
|
|
@@ -2393,7 +2418,8 @@ async function printBeneficialOwners(
|
|
|
2393
2418
|
heading: `${listingHeading(identity)}${cliStyles.muted(beneficialListFacts(payload, form, rows.length, history).map((fact) => ` · ${fact}`).join(""))}`,
|
|
2394
2419
|
// Text draws each column as the HDS report does; CSV and JSON keep the raw values.
|
|
2395
2420
|
textColumns: BENEFICIAL_REPORT_COLUMNS.map((column) => ({ ...column, format: column.textFormat ?? column.format })),
|
|
2396
|
-
|
|
2421
|
+
// A foreign line's owners read from its company's home line say whose.
|
|
2422
|
+
...(legend || payload.companyFrom ? { footnote: [legend, ...companyDataNotes(payload.companyFrom)].filter(Boolean).join("\n") } : {}),
|
|
2397
2423
|
columns: [
|
|
2398
2424
|
...BENEFICIAL_REPORT_COLUMNS.map(({ key, header, align }) => ({ key, header, ...(align ? { align } : {}) })),
|
|
2399
2425
|
{ key: "otherPersons", header: "Other persons" },
|
|
@@ -2494,7 +2520,7 @@ async function runHolders(
|
|
|
2494
2520
|
const columns = holderReportColumns(shownRows, valueCurrency);
|
|
2495
2521
|
// Dated as HDS dates it: by the list's as-of; an insider report of Form 4 filings alone as INS dates it, by the newest.
|
|
2496
2522
|
const freshness = form4Only
|
|
2497
|
-
? rowsFreshness(form4?.newestFiled ? [{ filingDate: form4.newestFiled }] : [], { ...
|
|
2523
|
+
? rowsFreshness(form4?.newestFiled ? [{ filingDate: form4.newestFiled }] : [], { ...SEC_FILING_ROWS, basis: "Form 4 filings" })
|
|
2498
2524
|
: rowsFreshness(shownRows, { ...SEC_FILINGS, basis: "13F filings" }, { asOf: data.asOf ?? null });
|
|
2499
2525
|
// Named as fn HDS takes it, so the closing notes point to the same listing.
|
|
2500
2526
|
const requested = exchangeOption && !raw.includes(":") ? `${raw}:${exchangeOption.toUpperCase()}` : raw;
|
|
@@ -2564,6 +2590,8 @@ async function runHolders(
|
|
|
2564
2590
|
insiderCommand ? insiderSummary(data, listing, identity, form4) : "",
|
|
2565
2591
|
].filter(Boolean).join("\n"),
|
|
2566
2592
|
empty: `No holders reported for ${listingTitle(identity)}.`,
|
|
2593
|
+
// Holders read from the company's home line say whose; the SEC index's are the listing's own.
|
|
2594
|
+
...(!indexed && data.companyFrom ? { footnote: companyDataNotes(data.companyFrom).join("\n") } : {}),
|
|
2567
2595
|
});
|
|
2568
2596
|
});
|
|
2569
2597
|
}
|
|
@@ -3257,7 +3285,7 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
3257
3285
|
title: "Bars",
|
|
3258
3286
|
lines: [
|
|
3259
3287
|
`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.`,
|
|
3260
|
-
"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. --json rows carry currency, unit, interval and flag.",
|
|
3288
|
+
"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. A low under half the open and close, or a high over twice them, is left blank alone and flagged low<close/2 or high>2*open. --json rows carry currency, unit, interval and flag.",
|
|
3261
3289
|
"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.",
|
|
3262
3290
|
"The heading's last line is the max drawdown of the bars shown: the largest fall from a close to a later close, with the peak and the trough, their dates, and the date a close got back to the peak, if one did (maxDrawdown in --json).",
|
|
3263
3291
|
],
|
|
@@ -82,6 +82,28 @@ export async function searchNewsStories(text: string, limit: number, topics?: re
|
|
|
82
82
|
return response.items.map((item) => mapCloudNewsArticle(item));
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/** A search's stories, and whether they are its closest ones because no story has every word. */
|
|
86
|
+
export interface NewsSearchAnswer {
|
|
87
|
+
found: NewsArticle[];
|
|
88
|
+
closest: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The stories matching the text; when no story has every word of a search of
|
|
93
|
+
* several, the ones with the most of them, most words first. Spellings of
|
|
94
|
+
* one word are one word to the service: lockup, lock-up and lock up;
|
|
95
|
+
* spinoff and spin-off; expiry and expiration.
|
|
96
|
+
*/
|
|
97
|
+
export async function searchClosestNewsStories(text: string, limit: number): Promise<NewsSearchAnswer> {
|
|
98
|
+
const response = await apiClient.searchCloudNews(text, limit, undefined, { closest: true });
|
|
99
|
+
return { found: response.items.map((item) => mapCloudNewsArticle(item)), closest: response.match === "closest" };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** What the heading says of a search no story has every word of. */
|
|
103
|
+
export function closestStoriesReason(text: string): string {
|
|
104
|
+
return `no story has every word of "${text}"; the closest, most words first`;
|
|
105
|
+
}
|
|
106
|
+
|
|
85
107
|
/** The NI code of the metal markets: prices, futures, bullion and the trusts that hold it, not a miner's releases. */
|
|
86
108
|
export const METALS_NEWS_TOPIC = "metals";
|
|
87
109
|
|
|
@@ -2,7 +2,7 @@ import { logFailure } from "../../utils/log-failure";
|
|
|
2
2
|
import { apiClient } from "../../api-client";
|
|
3
3
|
import { ApiRequestError } from "../../api-client/errors";
|
|
4
4
|
import type { CliCommandDef } from "../../types/plugin";
|
|
5
|
-
import { withCliServices, withMarketData } from "../context";
|
|
5
|
+
import { withCliServices, withConfigData, withMarketData } from "../context";
|
|
6
6
|
import { CLI_ERROR_CODE, failWithFields } from "../errors";
|
|
7
7
|
import type { CliResultColumn } from "../result";
|
|
8
8
|
import { formatCompact } from "../../utils/format";
|
|
@@ -25,17 +25,27 @@ import {
|
|
|
25
25
|
import { MOVER_CANDIDATES } from "../../plugins/builtin/market-movers/client";
|
|
26
26
|
import { hasPartialFloat, meetsMinFloat, minFloatShares, normalizeMinFloat, PARTIAL_FLOAT_NOTE } from "../../plugins/builtin/market-movers/min-float";
|
|
27
27
|
import { fetchEconCalendarFrom } from "../../plugins/builtin/econ/calendar-source";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
attachFredSeriesPersistence, computedFredSeriesNote, loadCachedFredSeries, resetFredSeriesPersistence, seriesNotOnFred,
|
|
30
|
+
} from "../../sources/gloomberb-cloud/fred-series";
|
|
31
|
+
import type { CloudFredSeriesPayload } from "../../api-client";
|
|
32
|
+
import { savedCopyMark } from "../../data/saved-copies";
|
|
33
|
+
import { ratesMacroPluginMeta } from "../../plugins/builtin/builtin-plugin-meta";
|
|
34
|
+
import { createCommandPluginPersistence } from "../saved-copy-persistence";
|
|
29
35
|
import { EXTENDED_SESSION_LABELS, getQuoteSessionFields, type ExtendedSession } from "../../market-data/market/status";
|
|
30
36
|
import {
|
|
31
37
|
COUNTRY_CYCLE,
|
|
32
38
|
COUNTRY_FILTER_HINT,
|
|
39
|
+
awaitsConsensus,
|
|
33
40
|
countriesWithoutEvents,
|
|
41
|
+
econEventName,
|
|
34
42
|
FILTER_CYCLE,
|
|
35
43
|
loadCalendar,
|
|
36
44
|
eventCountry,
|
|
37
45
|
matchesCountry,
|
|
46
|
+
matchesEventSearch,
|
|
38
47
|
matchesImpact,
|
|
48
|
+
NO_CONSENSUS_NOTE,
|
|
39
49
|
noScheduledReleasesMessage,
|
|
40
50
|
parseCountryFilter,
|
|
41
51
|
shortCalendarEnd,
|
|
@@ -303,7 +313,7 @@ function utcDateTimePart(value: unknown, part: "date" | "time", readerClock = tr
|
|
|
303
313
|
return part === "date" ? iso.slice(0, 10) : `${iso.slice(11, 16)} UTC${readerClock ? localTimeSuffix(date.getTime()) : ""}`;
|
|
304
314
|
}
|
|
305
315
|
|
|
306
|
-
const ECON_USAGE = "econ [--country <regions or countries>] [--impact <level>] [--from <date>] [--tz <zone>]";
|
|
316
|
+
const ECON_USAGE = "econ [--country <regions or countries>] [--impact <level>] [--search <words>] [--from <date>] [--tz <zone>]";
|
|
307
317
|
const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as const;
|
|
308
318
|
/** "Sat" for 2026-10-31. */
|
|
309
319
|
const weekdayOf = (date: string) => WEEKDAYS[new Date(`${date}T00:00:00Z`).getUTCDay()]!;
|
|
@@ -470,13 +480,15 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
470
480
|
const rawArgs = [...args];
|
|
471
481
|
const country = parseCountry(takeOption(rawArgs, "--country"), ctx);
|
|
472
482
|
const impact: ImpactFilter = parseFilter(takeOption(rawArgs, "--impact"), FILTER_CYCLE, "impact", ctx);
|
|
483
|
+
const search = takeOption(rawArgs, "--search")?.trim() ?? null;
|
|
484
|
+
if (search === "") ctx.fail("--search needs the words of an event's name.", "Such as econ --search nonfarm.");
|
|
473
485
|
const fromOption = parseDateOption(takeOption(rawArgs, "--from"), "--from", ctx);
|
|
474
486
|
const tzOption = takeOption(rawArgs, "--tz");
|
|
475
487
|
const viewerZone = tzOption == null ? null : canonicalTimeZone(tzOption);
|
|
476
488
|
if (tzOption != null && !viewerZone) {
|
|
477
489
|
ctx.fail(`Unknown time zone "${tzOption}".`, "Use an IANA zone name, such as America/New_York, Europe/London or Asia/Tokyo.");
|
|
478
490
|
}
|
|
479
|
-
rejectArgs(rawArgs, ECON_USAGE, ctx, "Filter with --country and --
|
|
491
|
+
rejectArgs(rawArgs, ECON_USAGE, ctx, "Filter with --country, --impact and --search.");
|
|
480
492
|
await withCliServices(ctx, async () => {
|
|
481
493
|
const now = Date.now();
|
|
482
494
|
const todayStart = Math.floor(now / DAY_MS) * DAY_MS;
|
|
@@ -487,7 +499,9 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
487
499
|
: null;
|
|
488
500
|
const past = stored?.events ? stored : null;
|
|
489
501
|
const unread = stored && !stored.events && stored.failure ? `The stored calendar could not be read (${stored.failure}); listing the current calendar` : null;
|
|
490
|
-
const { data: events, fetchedAt, stale } = past
|
|
502
|
+
const { data: events, fetchedAt, stale, source } = past
|
|
503
|
+
? { data: past.events, fetchedAt: Date.now(), stale: false, source: "network" as const }
|
|
504
|
+
: await loadCalendar(ctx.cliOptions.refresh);
|
|
491
505
|
// The calendar lists from today (UTC). --from moves the first day; --tail
|
|
492
506
|
// alone keeps the newest events of the whole calendar, the past week's too.
|
|
493
507
|
const from = fromOption ?? (ctx.cliOptions.tail != null ? null : new Date(todayStart).toISOString().slice(0, 10));
|
|
@@ -506,9 +520,10 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
506
520
|
// Countries asked for that nothing in the window is for: a quiet week there, not an unknown country.
|
|
507
521
|
const inWindow = events.filter((event) => event.date.getTime() >= fromTime);
|
|
508
522
|
const unscheduled = countriesWithoutEvents(country, inWindow);
|
|
509
|
-
const
|
|
510
|
-
.filter((event) => matchesCountry(event, country) && matchesImpact(event, impact))
|
|
511
|
-
.sort((left, right) => left.date.getTime() - right.date.getTime())
|
|
523
|
+
const listed = inWindow
|
|
524
|
+
.filter((event) => matchesCountry(event, country) && matchesImpact(event, impact) && (search == null || matchesEventSearch(event, search)))
|
|
525
|
+
.sort((left, right) => left.date.getTime() - right.date.getTime());
|
|
526
|
+
const rows = listed
|
|
512
527
|
.map((event) => {
|
|
513
528
|
// The viewer's clock (--tz) for a row with a time: its date and time there.
|
|
514
529
|
const clock = viewerZone && !hasNoReleaseTime(event) ? zonedClock(event.date.getTime(), viewerZone) : null;
|
|
@@ -522,7 +537,8 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
522
537
|
...(viewerZone ? { viewerDate: clock?.date ?? null, viewerTime: clock?.time ?? null } : {}),
|
|
523
538
|
country: eventCountry(event),
|
|
524
539
|
impact: event.impact,
|
|
525
|
-
|
|
540
|
+
// As the calendar names it: the Fed's decision is the upper bound of its range.
|
|
541
|
+
event: econEventName(event),
|
|
526
542
|
actual: figures.actual,
|
|
527
543
|
forecast: figures.forecast,
|
|
528
544
|
prior: figures.prior,
|
|
@@ -553,15 +569,20 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
553
569
|
// A filter of countries only, none of which has anything in the window, says so as its empty list.
|
|
554
570
|
const allUnscheduled = unscheduled.length > 0 && unscheduled.length === country.split(",").length;
|
|
555
571
|
const notes = [listedBefore, listedAfter, ...(rows.length > 0 ? blankTimeNotes(rows) : []),
|
|
556
|
-
rows.length > 0 && unscheduled.length > 0 ? noScheduledReleasesMessage(unscheduled) : null
|
|
572
|
+
rows.length > 0 && unscheduled.length > 0 ? noScheduledReleasesMessage(unscheduled) : null,
|
|
573
|
+
// A dash under Forecast on a release still to come is a consensus not in yet, not a missing figure.
|
|
574
|
+
listed.some(awaitsConsensus) ? NO_CONSENSUS_NOTE : null]
|
|
557
575
|
.filter((note): note is string => !!note);
|
|
558
576
|
const footnote = notes.join("\n");
|
|
577
|
+
// The calendar as read, not its rows: those are scheduled, mostly ahead. One that could not be
|
|
578
|
+
// refreshed is the copy an earlier read saved, and says when that was.
|
|
579
|
+
const read = rowsFreshness([], { status: "not-a-feed", basis: "calendar", asOf: fetchedAt }, { stale });
|
|
580
|
+
const calendarFreshness = read && source === "stale-fallback" ? { ...read, savedAt: new Date(fetchedAt).toISOString() } : read;
|
|
559
581
|
ctx.printResult({
|
|
560
582
|
data: rows,
|
|
561
583
|
...(unread ? { warnings: [unread] } : {}),
|
|
562
|
-
metadata: { country, impact, from, listedThrough, storedFrom: past?.storedFrom ?? null, ...(viewerZone ? { timeZone: viewerZone } : {}) },
|
|
563
|
-
|
|
564
|
-
freshness: rowsFreshness([], { status: "not-a-feed", basis: "calendar", asOf: fetchedAt }, { stale }),
|
|
584
|
+
metadata: { country, impact, ...(search != null ? { search } : {}), from, listedThrough, storedFrom: past?.storedFrom ?? null, ...(viewerZone ? { timeZone: viewerZone } : {}) },
|
|
585
|
+
freshness: calendarFreshness,
|
|
565
586
|
}, {
|
|
566
587
|
dateKey: "date",
|
|
567
588
|
defaultLimit: 50,
|
|
@@ -607,6 +628,35 @@ function isUnsupportedSeries(error: unknown): boolean {
|
|
|
607
628
|
return error.details?.code === "unsupported_series" || /^Unsupported FRED series\b/i.test(message);
|
|
608
629
|
}
|
|
609
630
|
|
|
631
|
+
/**
|
|
632
|
+
* Runs `run` with the FRED series cache saving what it fetches in the profile,
|
|
633
|
+
* where the app's rates and macro panes keep it: a refresh that fails then
|
|
634
|
+
* answers from the copy an earlier read saved, and the report says so.
|
|
635
|
+
*/
|
|
636
|
+
function withSavedFredSeries<T>(ctx: Parameters<CliCommandDef["execute"]>[1], run: () => Promise<T>): Promise<T> {
|
|
637
|
+
return withConfigData(ctx, async ({ persistence }) => {
|
|
638
|
+
attachFredSeriesPersistence(createCommandPluginPersistence(persistence.resources, `plugin:${ratesMacroPluginMeta.stateId}`));
|
|
639
|
+
try {
|
|
640
|
+
return await run();
|
|
641
|
+
} finally {
|
|
642
|
+
resetFredSeriesPersistence();
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* A FRED series from Gloom Cloud, or the copy an earlier read saved when it
|
|
649
|
+
* cannot be refreshed (marked as such). A refusal (a series Gloom Cloud no
|
|
650
|
+
* longer serves, access denied) is said, not covered: `loadCachedFredSeries`
|
|
651
|
+
* throws it.
|
|
652
|
+
*/
|
|
653
|
+
async function loadFredSeries(
|
|
654
|
+
seriesId: string,
|
|
655
|
+
params: { startDate?: string; limit?: number; sortOrder: "asc" | "desc" },
|
|
656
|
+
): Promise<CloudFredSeriesPayload> {
|
|
657
|
+
return (await loadCachedFredSeries({ seriesId, ...params }, () => apiClient.getCloudFredSeries(seriesId, params))).data;
|
|
658
|
+
}
|
|
659
|
+
|
|
610
660
|
async function runFredList(filter: string, ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
611
661
|
let catalog;
|
|
612
662
|
try {
|
|
@@ -652,9 +702,19 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
652
702
|
ctx.fail(change ? `Unknown change "${change}".` : "--change needs 1D or 1W.", "Use 1D (from the previous observation) or 1W (from a week before).");
|
|
653
703
|
}
|
|
654
704
|
const seriesId = requireOneArg(args, FRED_USAGE, "series", ctx).toUpperCase();
|
|
705
|
+
await withSavedFredSeries(ctx, () => printFredSeries(ctx, { seriesId, start, startDate, sortOrder, horizon }));
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
async function printFredSeries(ctx: Parameters<CliCommandDef["execute"]>[1], { seriesId, start, startDate, sortOrder, horizon }: {
|
|
709
|
+
seriesId: string;
|
|
710
|
+
start: string | undefined;
|
|
711
|
+
startDate: string;
|
|
712
|
+
sortOrder: "asc" | "desc";
|
|
713
|
+
horizon: ReturnType<typeof parseFredChange>;
|
|
714
|
+
}) {
|
|
655
715
|
// A change needs the observation it is measured from: the fetch reaches back before the first row shown.
|
|
656
716
|
const fetchStart = horizon ? fredChangeFetchStart(startDate) : startDate;
|
|
657
|
-
const data = await
|
|
717
|
+
const data = await loadFredSeries(seriesId, { startDate: fetchStart, sortOrder }).catch((error: unknown) => {
|
|
658
718
|
if (isUnsupportedSeries(error)) ctx.fail(`Unsupported FRED series ${seriesId}. List supported series with: gloom fred --list`);
|
|
659
719
|
throw error;
|
|
660
720
|
});
|
|
@@ -668,7 +728,9 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
668
728
|
const releasedOf = (observation: { publisher?: string; publishedAt?: string; releasedOn?: string }) =>
|
|
669
729
|
observation.releasedOn ?? (observation.publisher?.trim() ? observation.publishedAt : undefined);
|
|
670
730
|
// A value FRED does not list yet carries its publisher and when it published, in every export.
|
|
731
|
+
// A row from the copy an earlier read saved, the refresh having failed, carries its mark: the source line says so.
|
|
671
732
|
const exportRow = (observation: { date: string; value: number | null; publisher?: string; publishedAt?: string; releasedOn?: string }) => ({
|
|
733
|
+
...savedCopyMark(data),
|
|
672
734
|
date: observation.date,
|
|
673
735
|
value: fredExportValue(seriesId, observation.value),
|
|
674
736
|
...(changes ? { change: changes.get(observation.date)?.change ?? null, changeFrom: changes.get(observation.date)?.changeFrom ?? null } : {}),
|
|
@@ -687,7 +749,7 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
687
749
|
computedFredSeriesNote(seriesId),
|
|
688
750
|
].filter((note): note is string => note != null);
|
|
689
751
|
// Nothing on or after the start: name where the series ends, so an empty answer is not a dead end.
|
|
690
|
-
const latest = rows.length > 0 ? null : await
|
|
752
|
+
const latest = rows.length > 0 ? null : await loadFredSeries(seriesId, { limit: 1, sortOrder: "desc" })
|
|
691
753
|
.then((payload) => payload.observations[0] ?? null).catch(logFailure("cli.commands-overview", "Read latest economic series observation failed", null));
|
|
692
754
|
// 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.
|
|
693
755
|
if (rows.length === 0 && (ctx.cliOptions.format === "csv" || ctx.cliOptions.format === "ndjson")) {
|
|
@@ -759,10 +821,12 @@ async function runYieldCurve(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
759
821
|
const startDate = parseStartDate(takeOption(args, "--start"), ctx);
|
|
760
822
|
rejectArgs(args, "yield-curve [--start <date>]", ctx);
|
|
761
823
|
const series = Object.keys(YIELD_TENORS);
|
|
762
|
-
const results = await Promise.all(series.map(async (seriesId) => {
|
|
763
|
-
const data = await
|
|
824
|
+
const results = await withSavedFredSeries(ctx, () => Promise.all(series.map(async (seriesId) => {
|
|
825
|
+
const data = await loadFredSeries(seriesId, { startDate, sortOrder: "desc" });
|
|
764
826
|
const [latest, previous] = data.observations;
|
|
765
827
|
return {
|
|
828
|
+
// A tenor from the copy an earlier read saved, the refresh having failed, says so in the source line.
|
|
829
|
+
...savedCopyMark(data),
|
|
766
830
|
seriesId,
|
|
767
831
|
date: latest?.date ?? "",
|
|
768
832
|
// Yields in percent, which the exports' `currency_or_unit` column says (`pct`), as `fred` does.
|
|
@@ -775,7 +839,7 @@ async function runYieldCurve(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
775
839
|
latest,
|
|
776
840
|
publishedAt: latest?.publisher ? null : fredPublicationTime(data.info, latest?.date),
|
|
777
841
|
};
|
|
778
|
-
}));
|
|
842
|
+
})));
|
|
779
843
|
// Tenors whose latest yield FRED does not list yet name the Treasury and when it published them.
|
|
780
844
|
const ahead = fredProvenance(results.flatMap((row) => row.latest ? [row.latest] : []));
|
|
781
845
|
// The curve shown was published with its last tenor: the newest of their FRED updates, when each has one.
|
|
@@ -907,10 +971,11 @@ export const overviewCliCommands: CliCommandDef[] = [
|
|
|
907
971
|
options: [
|
|
908
972
|
{ flags: "--country <regions or countries>", description: "US, G7, EU, all (default), or countries by their two-letter codes, comma separated, such as MX or MX,BR,ZA" },
|
|
909
973
|
{ flags: "--impact <level>", description: "high, medium, low, or all (default all)" },
|
|
974
|
+
{ flags: "--search <words>", description: "Events whose name has these words, case, spaces and hyphens aside, such as nonfarm or fed funds" },
|
|
910
975
|
{ flags: "--from <date>", description: `First day listed, as ${DATE_OPTION_EXAMPLES} (default today, UTC); an earlier day lists past events from the stored calendar and says where it starts` },
|
|
911
976
|
{ flags: "--tz <zone>", description: "Add a column with each release's time on that clock, an IANA zone such as America/New_York; with the weekday when it falls on another day than the UTC date" },
|
|
912
977
|
],
|
|
913
|
-
examples: ["econ", "econ --country US --impact high", "econ --country MX,BR,ZA,IN,KR", "econ --from 2026-10-12 --country US",
|
|
978
|
+
examples: ["econ", "econ --country US --impact high", "econ --search nonfarm", "econ --country MX,BR,ZA,IN,KR", "econ --from 2026-10-12 --country US",
|
|
914
979
|
"econ --country BR --tz America/New_York", "econ --tail 20"],
|
|
915
980
|
},
|
|
916
981
|
execute: runEcon,
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
candidateExchange,
|
|
17
17
|
LISTING_SEARCH_LIMIT,
|
|
18
18
|
listingDistance,
|
|
19
|
+
primaryCompanyLine,
|
|
19
20
|
searchTickerCandidates,
|
|
20
21
|
type TickerSearchCandidate,
|
|
21
22
|
} from "../../tickers/search";
|
|
@@ -34,8 +35,7 @@ import { readBrokerIndexCode } from "../listing-arg";
|
|
|
34
35
|
import { knownGapMessage, commandPointer } from "../known-gaps";
|
|
35
36
|
import { instrumentTypeLabel } from "../../tickers/instrument-kind";
|
|
36
37
|
import { canonicalExchange, isUsListingExchange } from "../../utils/exchanges";
|
|
37
|
-
import { companyNameExtraWords, groupByCompany, sameCompany } from "../../tickers/company-name";
|
|
38
|
-
import { namedByService } from "../../tickers/company-name-listing";
|
|
38
|
+
import { companyLines, companyNameExtraWords, groupByCompany, namedByService, sameCompany } from "../../tickers/company-name";
|
|
39
39
|
import { findCatalogSeries, renderSeriesMatches, type SeriesMatch } from "../series-search";
|
|
40
40
|
import { centralBankAskedAbout, centralBankCiting, type CentralBank } from "../../utils/central-banks";
|
|
41
41
|
import { createPaneCatalog } from "../pane-functions/discovery";
|
|
@@ -283,17 +283,17 @@ function withHomeListingPrimary(query: string, candidates: TickerSearchCandidate
|
|
|
283
283
|
|| primary.symbol.toUpperCase() === query.toUpperCase()) return candidates;
|
|
284
284
|
const company = resolveSearchName(primary);
|
|
285
285
|
const marked = !!primary.result && namedByService(query, primary.result);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
&& (
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if (
|
|
286
|
+
// Never a line of another business its name is in (`companyLines`): Siemens Energy AG is not Siemens AG's.
|
|
287
|
+
const lines = companyLines(primary, candidates.filter((candidate) => candidate === primary || (candidate.kind === "search"
|
|
288
|
+
&& candidate.category !== "Funds & Derivatives" && (candidate.instrumentClass === "equity" || candidate.instrumentClass === "other"))),
|
|
289
|
+
(candidate) => ({
|
|
290
|
+
name: resolveSearchName(candidate), exchange: candidateExchange(candidate),
|
|
291
|
+
receipt: listingDistance(candidate) === 2, home: candidate.result?.homeListing === true,
|
|
292
|
+
})).filter((candidate) => candidate === primary || !marked || companyNameExtraWords(resolveSearchName(candidate), company) != null);
|
|
293
|
+
const home = primaryCompanyLine(primary, lines, (candidate) => ({
|
|
294
|
+
distance: listingDistance(candidate), score: candidate.result?.popularity ?? candidate.popularity ?? null,
|
|
295
|
+
}), { named: marked });
|
|
296
|
+
if (home === primary) return candidates;
|
|
297
297
|
const rest = candidates.filter((candidate) => candidate !== home);
|
|
298
298
|
const at = rest.indexOf(primary);
|
|
299
299
|
return [...rest.slice(0, at), { ...home, category: "Primary Listing" as const }, { ...primary, category: "Other Listings" as const }, ...rest.slice(at + 1)];
|