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
|
@@ -83,7 +83,7 @@ import { movedAwayChange } from "../../tickers/ticker-changes";
|
|
|
83
83
|
import { companyMemberships, loadCompanyMemberships, type CompanyMemberships, type ListMembership } from "../issuer-membership";
|
|
84
84
|
import { isNotATickerMessage } from "../not-a-ticker";
|
|
85
85
|
import { sharesOutstandingInReceipts } from "../../utils/depositary-receipt";
|
|
86
|
-
import { depositaryPairText, isReceiptSide, listingDepositaryPair } from "../../market-data/depositary-pair";
|
|
86
|
+
import { cedearRatioText, depositaryPairText, isReceiptSide, listingCedearRatio, listingDepositaryPair } from "../../market-data/depositary-pair";
|
|
87
87
|
import { cliFreshnessFooter } from "../result";
|
|
88
88
|
import { providerMissReason } from "../../sources/provider-errors";
|
|
89
89
|
import { exportEntriesTable, reportFooterLines, type CliReportTables } from "../report-tables";
|
|
@@ -100,6 +100,7 @@ import { bondFigures, NOT_SEC_YIELD } from "../../market-data/bond-funds";
|
|
|
100
100
|
import { readFundFacts } from "../../market-data/fund-facts";
|
|
101
101
|
import { taxEquivalentLines, type TaxEquivalentAnalysis } from "../../market-data/tax-equivalent";
|
|
102
102
|
import { isRoundTheClockCoin } from "../../utils/crypto-pair";
|
|
103
|
+
import { ipoDetailNotes, ipoMetricRows, loadTickerIpo, type TickerIpo } from "../../plugins/builtin/ipo-calendar/listing-ipo";
|
|
103
104
|
import { coinNewsFallbackNotice } from "../../sources/gloomberb-cloud/news";
|
|
104
105
|
|
|
105
106
|
const NEWS_ITEM_LIMIT = 5;
|
|
@@ -126,6 +127,8 @@ const METADATA_SEPARATOR = " · ";
|
|
|
126
127
|
interface TickerCommandDependencies {
|
|
127
128
|
/** `--exchange`: the listing, for a symbol that trades in several places. */
|
|
128
129
|
exchange?: string;
|
|
130
|
+
/** A stock's IPO of the last year with its lock-up news; a failure leaves the IPO section out. */
|
|
131
|
+
listingIpo?: (listing: { symbol: string; exchange?: string | null }) => Promise<TickerIpo | null>;
|
|
129
132
|
initMarketData?: () => Promise<MarketContext>;
|
|
130
133
|
fail?: (message: string, details?: string) => never;
|
|
131
134
|
printResult?: CliCommandContext["printResult"];
|
|
@@ -1081,8 +1084,11 @@ export async function buildTickerReport({
|
|
|
1081
1084
|
fundFacts = null,
|
|
1082
1085
|
memberships,
|
|
1083
1086
|
offerings = [],
|
|
1087
|
+
ipo = null,
|
|
1084
1088
|
}: {
|
|
1085
1089
|
symbol: string;
|
|
1090
|
+
/** A stock's IPO of the last year, with its lock-up estimate and news (loadTickerIpo). */
|
|
1091
|
+
ipo?: TickerIpo | null;
|
|
1086
1092
|
/** The lists holding the company, by any of its listings (loadCompanyMemberships); the saved listing's own lists when absent. */
|
|
1087
1093
|
memberships?: CompanyMemberships;
|
|
1088
1094
|
/** The filings behind the latest statements, and a newer quarter's results release. */
|
|
@@ -1165,6 +1171,9 @@ export async function buildTickerReport({
|
|
|
1165
1171
|
if (depositaryPair) {
|
|
1166
1172
|
lines.push(cliStyles.muted(`ADR ratio ${depositaryPairText(depositaryPair, isReceiptSide(financials))}. Implied FX and premium: gloom fn ADR ${quote?.symbol ?? symbol}`));
|
|
1167
1173
|
}
|
|
1174
|
+
// A CEDEAR states its ratio in its company's shares, as a receipt does.
|
|
1175
|
+
const cedearRatio = listingCedearRatio(financials);
|
|
1176
|
+
if (cedearRatio) lines.push(cliStyles.muted(`CEDEAR ratio ${cedearRatioText(cedearRatio)}.`));
|
|
1168
1177
|
|
|
1169
1178
|
// A list that holds the company as another of its listings says which: "Watchlists R (as RCI.B:TSX)".
|
|
1170
1179
|
const lists = memberships ?? companyMemberships(config, tickerFile, [], null);
|
|
@@ -1250,7 +1259,15 @@ export async function buildTickerReport({
|
|
|
1250
1259
|
]);
|
|
1251
1260
|
}
|
|
1252
1261
|
|
|
1253
|
-
|
|
1262
|
+
// A stock that listed in the last year: its IPO, the move since, and when its lock-up is estimated to end.
|
|
1263
|
+
if (ipo) {
|
|
1264
|
+
appendMetricSection(lines, "IPO", ipoMetricRows(ipo.deal, quote ? getRegularSessionDisplay(quote)?.price : null));
|
|
1265
|
+
for (const note of ipoDetailNotes(ipo.deal, ipo.news)) lines.push(cliStyles.muted(note));
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// A listing of the last year may have no statements yet: its IPO says how recent it is.
|
|
1269
|
+
const listing = { listedSince: ipo ? ipo.deal.listingDate ?? ipo.deal.firstDay?.session ?? null : null };
|
|
1270
|
+
const metrics = fundamentalsMetrics(quote, priced.fundamentals, profile, { text: marketCapText }, priceReturns, fundamentalsCurrency(financials, listing),
|
|
1254
1271
|
{ text: enterpriseValueText }, distributions, bondFund, codes);
|
|
1255
1272
|
const reportedCurrency = financials.financialCurrency?.trim();
|
|
1256
1273
|
const statements = [...financials.annualStatements, ...financials.quarterlyStatements];
|
|
@@ -1349,9 +1366,11 @@ function buildTickerStructuredData({
|
|
|
1349
1366
|
bondFund = null,
|
|
1350
1367
|
memberships,
|
|
1351
1368
|
offerings = [],
|
|
1369
|
+
ipo = null,
|
|
1352
1370
|
}: {
|
|
1353
1371
|
symbol: string;
|
|
1354
1372
|
listing: CliListing;
|
|
1373
|
+
ipo?: TickerIpo | null;
|
|
1355
1374
|
memberships: CompanyMemberships;
|
|
1356
1375
|
statementFilings: StatementFilingsReport | null;
|
|
1357
1376
|
offerings?: readonly OfferingFiling[];
|
|
@@ -1434,6 +1453,8 @@ function buildTickerStructuredData({
|
|
|
1434
1453
|
// and the offering prospectuses filed since that the cash does not include.
|
|
1435
1454
|
cashRunway: runway,
|
|
1436
1455
|
offeringsAfterBalanceSheet: runway ? offeringsAfter(offerings, runway.periodEnd) : [],
|
|
1456
|
+
// A stock that listed in the last year: its IPO deal (offer price, first day, lock-up estimate) and lock-up news.
|
|
1457
|
+
...ipo ? { ipo: { ...ipo.deal, lockupNews: ipo.news } } : {},
|
|
1437
1458
|
// The form and filed date of the latest periods' filings, and a newer quarter's results released before its statements.
|
|
1438
1459
|
statementFilings,
|
|
1439
1460
|
notes,
|
|
@@ -1462,6 +1483,8 @@ function buildTickerStructuredData({
|
|
|
1462
1483
|
}
|
|
1463
1484
|
|
|
1464
1485
|
function depositaryPairJson(financials: TickerFinancials) {
|
|
1486
|
+
const cedear = listingCedearRatio(financials);
|
|
1487
|
+
if (cedear) return { cedearRatio: { ...cedear, text: cedearRatioText(cedear) } };
|
|
1465
1488
|
const pair = listingDepositaryPair(financials);
|
|
1466
1489
|
if (!pair) return {};
|
|
1467
1490
|
const receipt = isReceiptSide(financials);
|
|
@@ -1573,6 +1596,11 @@ export async function ticker(symbol: string, dependencies: TickerCommandDependen
|
|
|
1573
1596
|
});
|
|
1574
1597
|
// An offering since the latest balance sheet, read only when the report has a runway to flag.
|
|
1575
1598
|
const offerings = loadOfferingFilings(statementSources, { symbol: requestSymbol, exchange }, cashRunway(resolvedFinancials) != null);
|
|
1599
|
+
// A stock's IPO of the last year, read beside the rest; it never fails the report.
|
|
1600
|
+
const ipoLoad = quote && resolveTickerInstrumentKind(tickerFile, resolvedFinancials) === "equity"
|
|
1601
|
+
? (dependencies.listingIpo ?? loadTickerIpo)({ symbol: requestSymbol, exchange: exchange || quote.listingExchangeName || quote.exchangeName })
|
|
1602
|
+
.catch(logFailure("cli.commands-ticker", "Read a listing's IPO failed", null))
|
|
1603
|
+
: Promise.resolve(null);
|
|
1576
1604
|
const [notesResult, newsResult, secFilingsResult] = await Promise.allSettled([
|
|
1577
1605
|
notesFiles.load(tickerFile?.metadata.ticker ?? normalized),
|
|
1578
1606
|
dataProvider.getNews({
|
|
@@ -1640,6 +1668,7 @@ export async function ticker(symbol: string, dependencies: TickerCommandDependen
|
|
|
1640
1668
|
bondFund: await bondFund,
|
|
1641
1669
|
memberships,
|
|
1642
1670
|
offerings: await offerings,
|
|
1671
|
+
ipo: await ipoLoad,
|
|
1643
1672
|
}),
|
|
1644
1673
|
});
|
|
1645
1674
|
return;
|
|
@@ -1666,6 +1695,7 @@ export async function ticker(symbol: string, dependencies: TickerCommandDependen
|
|
|
1666
1695
|
fundFacts: await fundFacts,
|
|
1667
1696
|
memberships,
|
|
1668
1697
|
offerings: await offerings,
|
|
1698
|
+
ipo: await ipoLoad,
|
|
1669
1699
|
}));
|
|
1670
1700
|
if (freshness) console.log(`\n${cliFreshnessFooter(freshness, quote ?? resolvedFinancials)}`);
|
|
1671
1701
|
});
|
package/src/cli/context.ts
CHANGED
|
@@ -141,6 +141,7 @@ export async function initCliServices(options: CliServicesOptions = {}) {
|
|
|
141
141
|
config,
|
|
142
142
|
plugins: getLoadablePlugins(options.externalPlugins ?? []),
|
|
143
143
|
externalPlugins: options.externalPlugins ?? [],
|
|
144
|
+
interactive: false,
|
|
144
145
|
});
|
|
145
146
|
services.providerRouter.setConfigAccessor(() => config);
|
|
146
147
|
services.providerRouter.setBackgroundRevalidation(false);
|
package/src/cli/errors.ts
CHANGED
|
@@ -13,7 +13,7 @@ export const CLI_ERROR_CODE = "cli_error";
|
|
|
13
13
|
/** The code of a command that ran, and the data service has nothing for what it asked. */
|
|
14
14
|
export const NO_DATA_CODE = "no_data";
|
|
15
15
|
/** The code of a command whose requests never reached the data service. */
|
|
16
|
-
|
|
16
|
+
const SERVICE_UNREACHABLE_CODE = "service_unreachable";
|
|
17
17
|
|
|
18
18
|
/** What to check when nothing answered: the connection, and the proxy when one is set. */
|
|
19
19
|
function unreachableHint(): string {
|
|
@@ -74,6 +74,21 @@ export function failRetryable(message: string, code: string): never {
|
|
|
74
74
|
throw new CliFailure(message, undefined, code, true);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* What a command says when its requests never reached the data service:
|
|
79
|
+
* `message` names what it could not get (by default only that), and the
|
|
80
|
+
* details what to check. `--json` marks it retryable.
|
|
81
|
+
*/
|
|
82
|
+
export function unreachableError(message = DATA_SERVICE_UNREACHABLE_MESSAGE): CliErrorObject {
|
|
83
|
+
return { code: SERVICE_UNREACHABLE_CODE, message, details: unreachableHint(), retryable: true };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Fails as a command whose requests never reached the data service (`unreachableError`). */
|
|
87
|
+
export function failUnreachable(message?: string): never {
|
|
88
|
+
const error = unreachableError(message);
|
|
89
|
+
throw new CliFailure(error.message, error.details, error.code, error.retryable);
|
|
90
|
+
}
|
|
91
|
+
|
|
77
92
|
export function closeAndFail(persistence: AppPersistence, message: string, details?: string): never {
|
|
78
93
|
persistence.close();
|
|
79
94
|
fail(message, details);
|
|
@@ -104,7 +119,7 @@ function cliErrorObject(error: unknown): CliErrorObject {
|
|
|
104
119
|
}
|
|
105
120
|
// No answer came back at all: in Gloom's words, whichever fetch failed (Bun's "Unable to connect", a refused proxy).
|
|
106
121
|
if (isNetworkFailure(error)) {
|
|
107
|
-
return
|
|
122
|
+
return unreachableError();
|
|
108
123
|
}
|
|
109
124
|
// Gloom Cloud refused what was asked (an unknown series, a bad date): the input was wrong, not the program.
|
|
110
125
|
if (error instanceof ApiRequestError && error.status === 400) {
|
package/src/cli/glossary.ts
CHANGED
|
@@ -404,7 +404,8 @@ export const GLOSSARY: readonly GlossaryEntry[] = [
|
|
|
404
404
|
},
|
|
405
405
|
{
|
|
406
406
|
term: "Net debt", aliases: ["net debt/ebitda", "net debt to ebitda", "net leverage"], functions: ["FA"], shown: ["Net Debt"],
|
|
407
|
-
definition: "Total debt less cash and short-term investments
|
|
407
|
+
definition: "Total debt less cash and short-term investments, with the balance sheet's lease liabilities under IFRS (\"incl. leases\"); "
|
|
408
|
+
+ "a company reporting under US GAAP (a 10-K, or a 20-F such as Alibaba's) keeps its operating leases out (\"excl. op. leases\"). Negative means more cash than debt. "
|
|
408
409
|
+ "Net debt / EBITDA is how many years of EBITDA would repay it; FA divides by a year of EBITDA, the trailing four quarters in a quarterly column.",
|
|
409
410
|
},
|
|
410
411
|
{
|
|
@@ -437,6 +438,12 @@ export const GLOSSARY: readonly GlossaryEntry[] = [
|
|
|
437
438
|
definition: "How many ordinary shares one depositary receipt holds: 1 ADR = 10 ordinary shares means one GGAL in New York is ten GGAL.BA shares in Buenos Aires. "
|
|
438
439
|
+ "A receipt's price compares with its home price only once the home price is multiplied by the ratio.",
|
|
439
440
|
},
|
|
441
|
+
{
|
|
442
|
+
term: "CEDEAR ratio", aliases: ["cedear", "cedears", "certificado de deposito argentino"],
|
|
443
|
+
functions: ["DES"], shown: ["CEDEAR ratio", "CEDEAR Ratio"],
|
|
444
|
+
definition: "How many Buenos Aires CEDEARs make the company's shares they stand for, from BYMA's published list: 20 AAPL:BYMA = 1 Apple Inc. share. "
|
|
445
|
+
+ "A CEDEAR's market cap is its company's, in the currency of the listing it stands for (Apple's in dollars), not the certificate's peso price times a share count.",
|
|
446
|
+
},
|
|
440
447
|
{
|
|
441
448
|
term: "ADR premium", aliases: ["adr discount", "adr vs home", "receipt premium", "adr premium/discount"],
|
|
442
449
|
functions: ["ADR"], shown: ["ADR vs home", "premium", "discount"],
|
package/src/cli/history-rows.ts
CHANGED
|
@@ -14,7 +14,9 @@ import { isRoundTheClockCoin } from "../utils/crypto-pair";
|
|
|
14
14
|
import { currencyPairCode } from "../utils/currency-pair";
|
|
15
15
|
import { currencyUnitLabel, resolveCurrencyUnit } from "../utils/currency-units";
|
|
16
16
|
import { getPricePointTimestamp } from "../utils/price-history";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
isWickFlag, latestTradeIndex, missingVolumeNotice, pricePointIntegrityFlags, pricePointValues, pricePointWickFlags,
|
|
19
|
+
} from "../utils/price-history-integrity";
|
|
18
20
|
import { formatUtcTime } from "../utils/utc-time";
|
|
19
21
|
import { historyCoverageNotice } from "../sources/history-coverage";
|
|
20
22
|
import { barLastSession, barPeriodToDate, type BarPeriodToDate } from "../market-data/market/bar-period";
|
|
@@ -253,10 +255,17 @@ function flagCodes(row: Pick<HistoryRow, "flag">): string[] {
|
|
|
253
255
|
return row.flag ? row.flag.split(",") : [];
|
|
254
256
|
}
|
|
255
257
|
|
|
258
|
+
/** The codes of a bar left blank for prices that contradict each other: not a break before it, nor a withheld wick. */
|
|
259
|
+
function blankingCodes(row: Pick<HistoryRow, "flag">): string[] {
|
|
260
|
+
return flagCodes(row).filter((code) => !BREAK_FLAGS.has(code) && !isWickFlag(code));
|
|
261
|
+
}
|
|
262
|
+
|
|
256
263
|
/**
|
|
257
264
|
* Intraday bars keep their UTC time; daily and longer bars are trading dates.
|
|
258
265
|
* A bar whose prices contradict each other is blanked exactly as HP blanks it,
|
|
259
|
-
* and says why in `flag`. A
|
|
266
|
+
* and says why in `flag`. A low or high too far from the open and close to
|
|
267
|
+
* have traded is left blank, the rest of the bar kept, flagged `low<close/2`
|
|
268
|
+
* or `high>2*open`. A bar after a gap or a jump in the series keeps its
|
|
260
269
|
* prices and is flagged `gap` or `jump`.
|
|
261
270
|
*/
|
|
262
271
|
export function historyRows(
|
|
@@ -277,7 +286,7 @@ export function historyRows(
|
|
|
277
286
|
const time = times[index]!;
|
|
278
287
|
const iso = Number.isFinite(time) ? new Date(time).toISOString() : "";
|
|
279
288
|
const values = pricePointValues(point);
|
|
280
|
-
const flags = pricePointIntegrityFlags(point);
|
|
289
|
+
const flags = [...pricePointIntegrityFlags(point), ...pricePointWickFlags(point)];
|
|
281
290
|
const price = (value: number | null) => value == null ? null : cleanBarPrice(value, priceDivisor);
|
|
282
291
|
return {
|
|
283
292
|
date: intraday ? iso.replace(".000Z", "Z") : iso.slice(0, 10),
|
|
@@ -448,7 +457,7 @@ export function historyMissingVolumeNote(
|
|
|
448
457
|
if (!historyReportsVolume(rows)) return null;
|
|
449
458
|
const latestTrade = latestTradeIndex(rows.map((row) => Date.parse(row.date)));
|
|
450
459
|
const missing = rows.filter((row, index) => row.volume == null && row.venueVolume == null && !row.partial && !row.filled
|
|
451
|
-
&&
|
|
460
|
+
&& blankingCodes(row).length === 0 && index !== latestTrade).length;
|
|
452
461
|
return missingVolumeNotice(missing, rows.length) ?? null;
|
|
453
462
|
}
|
|
454
463
|
|
|
@@ -833,10 +842,9 @@ export function historyGapNote(
|
|
|
833
842
|
* keep their prices and are not counted.
|
|
834
843
|
*/
|
|
835
844
|
export function historyFlagNote(rows: readonly Pick<HistoryRow, "flag">[]): string | null {
|
|
836
|
-
const
|
|
837
|
-
const flagged = rows.filter((row) => contradictions(row).length > 0);
|
|
845
|
+
const flagged = rows.filter((row) => blankingCodes(row).length > 0);
|
|
838
846
|
if (flagged.length === 0) return null;
|
|
839
|
-
const codes = new Set(flagged.flatMap(
|
|
847
|
+
const codes = new Set(flagged.flatMap(blankingCodes));
|
|
840
848
|
const kinds = [
|
|
841
849
|
codes.has("high<open") || codes.has("high<close") ? "high below open or close" : "",
|
|
842
850
|
codes.has("low>open") || codes.has("low>close") ? "low above open or close" : "",
|
|
@@ -845,3 +853,23 @@ export function historyFlagNote(rows: readonly Pick<HistoryRow, "flag">[]): stri
|
|
|
845
853
|
const count = `${flagged.length} of ${rows.length} ${flagged.length === 1 && rows.length === 1 ? "bar" : "bars"}`;
|
|
846
854
|
return `${count} ${flagged.length === 1 ? "has" : "have"} ${kinds.join(", or ")} in the source data; their prices are left blank`;
|
|
847
855
|
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* "The low of 2021-11-29 is under half its open and close, too far to have
|
|
859
|
+
* traded; left blank and out of ranges", or null when no bar has such a wick
|
|
860
|
+
* (pricePointWickFlags). Three or more bars are counted.
|
|
861
|
+
*/
|
|
862
|
+
export function historyWickNote(rows: readonly Pick<HistoryRow, "date" | "flag">[]): string | null {
|
|
863
|
+
const flagged = rows.filter((row) => flagCodes(row).some(isWickFlag));
|
|
864
|
+
if (flagged.length === 0) return null;
|
|
865
|
+
const sides = new Set(flagged.flatMap((row) => flagCodes(row).filter(isWickFlag).map((code) => code.slice(0, code.indexOf(code.startsWith("low") ? "<" : ">")))));
|
|
866
|
+
const reason = "too far to have traded; left blank and out of ranges";
|
|
867
|
+
const only = flagged[0]!;
|
|
868
|
+
if (flagged.length === 1 && sides.size === 1) {
|
|
869
|
+
return sides.has("low") ? `The low of ${only.date} is under half its open and close, ${reason}`
|
|
870
|
+
: `The high of ${only.date} is over twice its open and close, ${reason}`;
|
|
871
|
+
}
|
|
872
|
+
const kinds = [sides.has("low") ? "a low under half" : "", sides.has("high") ? "a high over twice" : ""].filter(Boolean).join(" or ");
|
|
873
|
+
const dates = flagged.length <= LISTED_GAPS ? ` (${listItems(flagged.map((row) => row.date))})` : "";
|
|
874
|
+
return `${flagged.length} of ${rows.length} bars have ${kinds} their open and close${dates}, ${reason}`;
|
|
875
|
+
}
|
package/src/cli/index-roots.ts
CHANGED
|
@@ -35,10 +35,3 @@ export function indexRootTryLine(root: IndexRoot, command = "quote"): string {
|
|
|
35
35
|
export function indexRootAlsoLine(root: IndexRoot, command = "quote"): string {
|
|
36
36
|
return `also ${root.symbol} ${root.name} (gloom ${command} ${root.symbol})`;
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
/** Cboe's Treasury yield indices: 13-week bill, 5-, 10- and 30-year notes. Each level is a yield in percent. */
|
|
40
|
-
const YIELD_INDEX_SYMBOLS: ReadonlySet<string> = new Set(["^IRX", "^FVX", "^TNX", "^TYX"]);
|
|
41
|
-
|
|
42
|
-
export function isYieldIndexSymbol(symbol: string): boolean {
|
|
43
|
-
return YIELD_INDEX_SYMBOLS.has(symbol.trim().toUpperCase());
|
|
44
|
-
}
|
package/src/cli/known-gaps.ts
CHANGED
|
@@ -222,6 +222,17 @@ const TOPIC_POINTERS: ReadonlyArray<{
|
|
|
222
222
|
closestLabel: "COMEX curves",
|
|
223
223
|
closest: [["CTM", "gloom fn CTM HG for copper, gloom fn CTM ALI for aluminium"]],
|
|
224
224
|
},
|
|
225
|
+
{
|
|
226
|
+
// No source serves a jet fuel (kerosene) price. Heating oil, NY Harbor ULSD, is what airlines hedge it with and
|
|
227
|
+
// analysts read it by; the EIA's weekly US stocks are a catalog series (checked 2026-10-29: quote HO=F,
|
|
228
|
+
// fn COT HO and fn G CAP:doe:jet all answer).
|
|
229
|
+
topic: "Jet fuel (kerosene)",
|
|
230
|
+
availability: "no price series; heating oil (NY Harbor ULSD) is the usual proxy, its price: gloom quote HO=F. "
|
|
231
|
+
+ "US jet fuel stocks (EIA, weekly): gloom fn G CAP:doe:jet",
|
|
232
|
+
phrases: ["jet fuel", "jet fuels", "kerosene", "kerosine", "jet kerosene", "aviation fuel", "aviation fuels", "avtur"],
|
|
233
|
+
closestLabel: "positioning",
|
|
234
|
+
closest: [["COT", "heating oil, the usual jet fuel proxy: gloom fn COT HO"]],
|
|
235
|
+
},
|
|
225
236
|
{
|
|
226
237
|
// "exchange reserves" alone reads as crypto's; "foreign exchange reserves" is a central bank's, so only whole.
|
|
227
238
|
topic: "Crypto exchange reserves and exchange flows",
|
package/src/cli/listing-arg.ts
CHANGED
|
@@ -15,9 +15,9 @@ import {
|
|
|
15
15
|
type ListingVenue,
|
|
16
16
|
} from "../tickers/listing-venues";
|
|
17
17
|
import { findUsListingOfCompany, type UsListing } from "../tickers/us-listing";
|
|
18
|
-
import { findListingOfCompanyName, isCompanyNameInput, readTerminalCodeArg, type CompanyNameListing } from "../tickers/company-name-listing";
|
|
18
|
+
import { findListingOfCompanyName, isCompanyNameInput, marksAnotherListing, readTerminalCodeArg, type CompanyNameListing } from "../tickers/company-name-listing";
|
|
19
19
|
import { movedAwayChange, startTickerChangeLookup, type MovedTickerChange } from "../tickers/ticker-changes";
|
|
20
|
-
import { loadListingFate, MovedSymbolError, type ListingFate } from "../tickers/listing-fate";
|
|
20
|
+
import { lineMayHaveMoved, loadListingFate, MovedSymbolError, type ListingFate } from "../tickers/listing-fate";
|
|
21
21
|
import { quotedListingName } from "../tickers/listing-name";
|
|
22
22
|
import { cliStyles } from "../utils/cli-output";
|
|
23
23
|
import { groupByCompany, namesDepositaryReceipt, sameCompany, sameListedName, shortCompanyName } from "../tickers/company-name";
|
|
@@ -36,7 +36,7 @@ import { withMarketData } from "./scoped-context";
|
|
|
36
36
|
import { failWithFields } from "./errors";
|
|
37
37
|
import { stripStrayEdges } from "./symbol-args";
|
|
38
38
|
import { isNoProviderMessage, isNoSourceAnswer, isProviderBusy } from "../sources/provider-errors";
|
|
39
|
-
import { dashedCoinPair, MAJOR_COIN_NAMES } from "../utils/crypto-pair";
|
|
39
|
+
import { dashedCoinPair, exchangeLineCoinListing, isSpotCommodityOrCurrencyPair, MAJOR_COIN_NAMES } from "../utils/crypto-pair";
|
|
40
40
|
import { boardCoinFor, primeBoardCoins } from "./board-coins";
|
|
41
41
|
import { knownGapMessage } from "./known-gaps";
|
|
42
42
|
import { currencyPairSpelling } from "../utils/currency-pair";
|
|
@@ -107,6 +107,12 @@ export interface ListingArg {
|
|
|
107
107
|
* `readAs` asks about it, and a command the listing fails asks too.
|
|
108
108
|
*/
|
|
109
109
|
meant?: { symbol: string; name: string };
|
|
110
|
+
/**
|
|
111
|
+
* A bare symbol a company's name was read as still says what it was read
|
|
112
|
+
* as (`listingChoiceNotes`): GETYQ for "getty images", whose NYSE line
|
|
113
|
+
* became GETYQ. Other bare symbols say where the data service chose instead.
|
|
114
|
+
*/
|
|
115
|
+
readFromName?: true;
|
|
110
116
|
}
|
|
111
117
|
|
|
112
118
|
/** "Did you mean JPM (JPMorgan Chase & Co.)?", for a name read as a fund or an index; empty for any other listing. */
|
|
@@ -176,8 +182,13 @@ function parseCleanListingArg(
|
|
|
176
182
|
// `SAN:` asks which listing; with --exchange it has its answer.
|
|
177
183
|
if (option && typed.endsWith(":")) typed = typed.slice(0, -1);
|
|
178
184
|
const venueKey = parsePublicTickerKey(typed);
|
|
179
|
-
//
|
|
180
|
-
const
|
|
185
|
+
// A crypto exchange's line of a pair (BTC/GBP:COINBASE PRO, as search offered it) is the crypto venue's BTC-GBP:CCC.
|
|
186
|
+
const line = venueKey.exchange || optionCode ? exchangeLineCoinListing(venueKey.symbol, venueKey.exchange ?? optionCode) : null;
|
|
187
|
+
if (line) return { symbol: line.symbol, exchange: line.exchange, key: `${line.symbol}:${line.exchange}` };
|
|
188
|
+
// BTC/USD, as crypto venues write a pair, is the coin's own BTC-USD; spot crude (WTI/USD) and gold by the gram
|
|
189
|
+
// (GAU/USD:COMMODITY) are not coins, though tokens share their letters.
|
|
190
|
+
const coinPair = isSpotCommodityOrCurrencyPair(venueKey.symbol, venueKey.exchange ?? optionCode) ? null : dashedCoinPair(venueKey.symbol);
|
|
191
|
+
const parsed = { ...venueKey, symbol: coinPair ?? venueKey.symbol };
|
|
181
192
|
const ownCode = parsed.exchange ? typedExchangeCode(typed) : "";
|
|
182
193
|
let exchange = parsed.exchange ?? option;
|
|
183
194
|
let code = ownCode || optionCode;
|
|
@@ -452,7 +463,7 @@ export function coinListingNotes(
|
|
|
452
463
|
* its warnings.
|
|
453
464
|
*/
|
|
454
465
|
export async function listingChoiceNotes(
|
|
455
|
-
listing: Pick<CliListing, "symbol" | "exchange" | "saved" | "readAs">,
|
|
466
|
+
listing: Pick<CliListing, "symbol" | "exchange" | "saved" | "readAs" | "readFromName">,
|
|
456
467
|
deps: ListingDeps,
|
|
457
468
|
quote: Pick<Quote, "name" | "listingExchangeName" | "exchangeName" | "instrumentType" | "stale" | "delisting"> | null | undefined,
|
|
458
469
|
/** The venues `loadBareVenues` already started; looked up here when not given. */
|
|
@@ -460,6 +471,8 @@ export async function listingChoiceNotes(
|
|
|
460
471
|
): Promise<string[]> {
|
|
461
472
|
const coin = coinListingNotes(listing, quote);
|
|
462
473
|
if (!isBareListing(listing)) return [...listingReadNotes(listing), ...coin];
|
|
474
|
+
// A bare symbol a name was read as says so: "Read "getty images" as GETYQ (...). GETY became GETYQ on 2026-10-22."
|
|
475
|
+
if (listing.readFromName) return [...listingReadNotes(listing), ...coin];
|
|
463
476
|
if (!quote || UNLISTED_TYPES.has(quote.instrumentType?.trim().toUpperCase() ?? "")) return coin;
|
|
464
477
|
const exchange = canonicalExchange(quote.listingExchangeName || quote.exchangeName);
|
|
465
478
|
if (!exchange) return coin;
|
|
@@ -475,7 +488,7 @@ export async function listingChoiceNotes(
|
|
|
475
488
|
* as another listing (BTC, the Grayscale trust) ends with the coin's pair.
|
|
476
489
|
*/
|
|
477
490
|
export async function quotedListingChoiceNotes(
|
|
478
|
-
listing: Pick<CliListing, "symbol" | "exchange" | "saved" | "request" | "readAs">,
|
|
491
|
+
listing: Pick<CliListing, "symbol" | "exchange" | "saved" | "request" | "readAs" | "readFromName">,
|
|
479
492
|
deps: ListingDeps,
|
|
480
493
|
): Promise<string[]> {
|
|
481
494
|
const venues = loadBareVenues(listing, deps);
|
|
@@ -1325,6 +1338,22 @@ export async function oneWordCompanyName(typed: string, deps: Pick<ListingDeps,
|
|
|
1325
1338
|
return found ? companyListingArg(typed, found) : null;
|
|
1326
1339
|
}
|
|
1327
1340
|
|
|
1341
|
+
/**
|
|
1342
|
+
* A word the service marks as a company's name (`searchAliases`), read as
|
|
1343
|
+
* that company's listing as `oneWordCompanyName` reads a word, for a bare
|
|
1344
|
+
* symbol no source quotes, before it is read on the symbol's listings abroad:
|
|
1345
|
+
* "siemens" is SIE on XETRA (Siemens AG), not SIEMENS on NSE (Siemens Ltd.).
|
|
1346
|
+
* Null when the service marks no listing with the word, or marks the word's
|
|
1347
|
+
* own symbol, and when search names no company or is too busy to say.
|
|
1348
|
+
*/
|
|
1349
|
+
export async function serviceNamedCompany(typed: string, deps: Pick<ListingDeps, "dataProvider">): Promise<ListingArg | null> {
|
|
1350
|
+
// Most symbols are no company's name: one search says so before any listing is weighed or quoted.
|
|
1351
|
+
if (!ONE_WORD_NAME.test(typed.trim()) || !await marksAnotherListing(deps.dataProvider, typed)) return null;
|
|
1352
|
+
const found = await findOneWordCompany(typed, deps);
|
|
1353
|
+
if (!found?.named || found.symbol.toUpperCase() === typed.trim().toUpperCase()) return null;
|
|
1354
|
+
return companyListingArg(typed, found);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1328
1357
|
/** The company `oneWordCompanyName` reads a word as, before it is read as a listing. */
|
|
1329
1358
|
async function findOneWordCompany(typed: string, deps: Pick<ListingDeps, "dataProvider">): Promise<CompanyNameListing | null> {
|
|
1330
1359
|
const word = typed.trim();
|
|
@@ -1346,7 +1375,36 @@ function isMajorCoinName(word: string): boolean {
|
|
|
1346
1375
|
}
|
|
1347
1376
|
|
|
1348
1377
|
function findCompanyName(typed: string, deps: Pick<ListingDeps, "dataProvider">, options: { oneWord?: boolean } = {}): Promise<CompanyNameListing | null> {
|
|
1349
|
-
return findListingOfCompanyName(deps.dataProvider, typed, { meant: true, quoted: true, ...options });
|
|
1378
|
+
return findListingOfCompanyName(deps.dataProvider, typed, { meant: true, quoted: true, movedLine: (listing) => movedLineOf(listing, deps), ...options });
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
/**
|
|
1382
|
+
* Where the company of a US line that stopped trading, or has no quote, trades
|
|
1383
|
+
* now when it moved off the symbol (`loadListingFate`): GETY became GETYQ on
|
|
1384
|
+
* 2026-10-22, so "getty images" is GETYQ rather than its delisted NYSE line or
|
|
1385
|
+
* the Munich line still quoting the old shares. A line that trades is not
|
|
1386
|
+
* looked up.
|
|
1387
|
+
*/
|
|
1388
|
+
async function movedLineOf(
|
|
1389
|
+
listing: { symbol: string; exchange: string; quote: Quote | null; name: string | null },
|
|
1390
|
+
deps: Pick<ListingDeps, "dataProvider">,
|
|
1391
|
+
): Promise<{ symbol: string; note: string } | null> {
|
|
1392
|
+
const { quote, name, ...line } = listing;
|
|
1393
|
+
if (quote && !lineMayHaveMoved(quote)) return null;
|
|
1394
|
+
const fate = await loadListingFate(line, deps.dataProvider, { quote });
|
|
1395
|
+
const moved = fate.moved ?? (quote ? null : movedWithoutQuote(fate.change, name));
|
|
1396
|
+
return moved ? { symbol: moved.tradesAs, note: moved.note } : null;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
/**
|
|
1400
|
+
* The move of a line with no quote, when it is the move of the company search
|
|
1401
|
+
* names on the line. With no quote nothing tells the company's change from an
|
|
1402
|
+
* older one's: a reused symbol's change data is the company that held it
|
|
1403
|
+
* before, so it is followed only when that company is this one.
|
|
1404
|
+
*/
|
|
1405
|
+
function movedWithoutQuote(change: ListingFate["change"], name: string | null): MovedTickerChange | null {
|
|
1406
|
+
const moved = movedAwayChange(change);
|
|
1407
|
+
return moved && sameCompany(name, moved.issuer) ? moved : null;
|
|
1350
1408
|
}
|
|
1351
1409
|
|
|
1352
1410
|
async function readCompanyName(typed: string, deps: Pick<ListingDeps, "dataProvider">, options: { oneWord?: boolean } = {}): Promise<ListingArg | null> {
|
|
@@ -1355,11 +1413,16 @@ async function readCompanyName(typed: string, deps: Pick<ListingDeps, "dataProvi
|
|
|
1355
1413
|
}
|
|
1356
1414
|
|
|
1357
1415
|
function companyListingArg(typed: string, found: CompanyNameListing): ListingArg {
|
|
1358
|
-
const key = `${found.symbol}:${found.exchange}
|
|
1416
|
+
const key = found.exchange ? `${found.symbol}:${found.exchange}` : found.symbol;
|
|
1359
1417
|
// A fund or an index carrying the name asks about the company's stock: "Did you mean JPM (JPMorgan Chase & Co.)?".
|
|
1360
1418
|
const meant = found.meant ? { symbol: found.meant.symbol, name: found.meant.name } : null;
|
|
1361
1419
|
const question = meant ? ` ${meantQuestion({ meant })}` : "";
|
|
1362
|
-
|
|
1420
|
+
// A company that moved off the line search lists says so: "GETY became GETYQ on 2026-10-22."
|
|
1421
|
+
const moved = found.moved ? ` ${found.moved}.` : "";
|
|
1422
|
+
return {
|
|
1423
|
+
...parseListingArg(key), readAs: `Read "${typed.trim()}" as ${key} (${found.name}).${moved}${question}`,
|
|
1424
|
+
...meant ? { meant } : {}, ...found.exchange ? {} : { readFromName: true as const },
|
|
1425
|
+
};
|
|
1363
1426
|
}
|
|
1364
1427
|
|
|
1365
1428
|
/**
|
|
@@ -1377,7 +1440,7 @@ export async function companyNameSuggestion(typed: string, deps: Pick<ListingDep
|
|
|
1377
1440
|
const found = isCompanyNameInput(words) ? await findCompanyName(words, deps).catch(busyAsMiss) : await findOneWordCompany(words, deps);
|
|
1378
1441
|
const listing = found?.meant ?? found;
|
|
1379
1442
|
if (!listing || listing.symbol.toUpperCase() === words.toUpperCase()) return null;
|
|
1380
|
-
return { symbol: isUsListingExchange(listing.exchange) ? listing.symbol : `${listing.symbol}:${listing.exchange}`, name: listing.name };
|
|
1443
|
+
return { symbol: !listing.exchange || isUsListingExchange(listing.exchange) ? listing.symbol : `${listing.symbol}:${listing.exchange}`, name: listing.name };
|
|
1381
1444
|
} catch (error) {
|
|
1382
1445
|
logFailure("cli.listing-arg", "Search a company name for a suggestion failed")(error);
|
|
1383
1446
|
return null;
|
|
@@ -12,7 +12,9 @@ import type {
|
|
|
12
12
|
PluginPersistence,
|
|
13
13
|
} from "../../types/plugin";
|
|
14
14
|
import { createPluginTeamState } from "../../plugins/team-state";
|
|
15
|
+
import { bindPluginContextNamespaces } from "../../plugins/registry/plugin-state";
|
|
15
16
|
import type { MarketContext } from "../types";
|
|
17
|
+
import { createCommandPluginPersistence } from "../saved-copy-persistence";
|
|
16
18
|
import type { PaneFunctionCatalog } from "./catalog";
|
|
17
19
|
|
|
18
20
|
export interface PaneDiscoveryOptions {
|
|
@@ -173,23 +175,41 @@ export async function createPaneCatalog(context: MarketContext, plugins: GloomPl
|
|
|
173
175
|
brokers,
|
|
174
176
|
});
|
|
175
177
|
|
|
178
|
+
// Each plugin's caches save what a command fetches where the app keeps them, so a failed refresh answers from them.
|
|
179
|
+
const resources = context.persistence?.resources;
|
|
180
|
+
const persistences: Array<ReturnType<typeof createCommandPluginPersistence>> = [];
|
|
181
|
+
const commandPersistence = (stateId: string) => {
|
|
182
|
+
const persistence = createCommandPluginPersistence(resources, `plugin:${stateId}`);
|
|
183
|
+
persistences.push(persistence);
|
|
184
|
+
return persistence;
|
|
185
|
+
};
|
|
186
|
+
|
|
176
187
|
const destroy = () => {
|
|
177
188
|
let failure: unknown;
|
|
178
189
|
for (const plugin of setupPlugins.splice(0).reverse()) {
|
|
179
190
|
try { plugin.dispose?.(); } catch (error) { failure ??= error; }
|
|
180
191
|
}
|
|
192
|
+
// A plugin that keeps its persistence past dispose must not reach a store the command closes.
|
|
193
|
+
for (const persistence of persistences.splice(0)) persistence.release();
|
|
181
194
|
brokers.clear();
|
|
182
195
|
if (context.resolveBroker === resolveBroker) context.resolveBroker = previousResolver;
|
|
183
196
|
if (failure) throw failure;
|
|
184
197
|
};
|
|
185
198
|
|
|
199
|
+
const namespaced = (stateId: string) => ({
|
|
200
|
+
persistence: commandPersistence(stateId),
|
|
201
|
+
resume: discoveryContext.resume,
|
|
202
|
+
configState: discoveryContext.configState,
|
|
203
|
+
teamState: discoveryContext.teamState,
|
|
204
|
+
});
|
|
186
205
|
try {
|
|
187
206
|
for (const plugin of plugins) {
|
|
188
207
|
for (const pane of plugin.panes ?? []) panes.set(pane.id, pane);
|
|
189
208
|
for (const template of plugin.paneTemplates ?? []) paneTemplates.set(template.id, template);
|
|
190
209
|
if (plugin.setup) {
|
|
191
210
|
setupPlugins.push(plugin);
|
|
192
|
-
|
|
211
|
+
const stateId = plugin.stateId ?? plugin.id;
|
|
212
|
+
await plugin.setup(bindPluginContextNamespaces({ ...discoveryContext, ...namespaced(stateId) }, namespaced));
|
|
193
213
|
}
|
|
194
214
|
}
|
|
195
215
|
} catch (error) {
|
|
@@ -123,6 +123,8 @@ export interface ReportFreshness {
|
|
|
123
123
|
ageOnly?: true;
|
|
124
124
|
/** What the newest observation is, said before its time: `newest story Tue 29 Sep 2026 20:17 UTC`. */
|
|
125
125
|
asOfLabel?: string;
|
|
126
|
+
/** The line says when the report was retrieved after the newest observation's date (`retrieved Wed 28 Oct 2026 19:07 UTC`). */
|
|
127
|
+
retrievedToo?: true;
|
|
126
128
|
/** What the figures are, said after the status: `funding = current estimate, pays 08:00 UTC`. */
|
|
127
129
|
note?: string;
|
|
128
130
|
/** Shorter forms of `note`, in the order a capture's line too long for it tries them. */
|
|
@@ -610,6 +612,7 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
|
|
|
610
612
|
...(oldestAgeMinutes != null ? { oldestAgeMinutes } : {}),
|
|
611
613
|
...(status === "unreported" && declared.ageOnly ? { ageOnly: true as const } : {}),
|
|
612
614
|
...(asOfLabel ? { asOfLabel } : {}),
|
|
615
|
+
...(asOf && declared.retrievedToo ? { retrievedToo: true as const } : {}),
|
|
613
616
|
...(note ? { note } : {}),
|
|
614
617
|
...(noteVariants.length > 0 ? { noteVariants } : {}),
|
|
615
618
|
...(schedule ? { schedule } : {}),
|
|
@@ -1061,8 +1064,11 @@ function statusLineParts(freshness: ReportFreshness, detail: StatusLineDetail =
|
|
|
1061
1064
|
const undated = detail.date === "none";
|
|
1062
1065
|
return [
|
|
1063
1066
|
...(freshness.sources?.length ? sourcesAsOfParts(freshness, freshness.sources, undated)
|
|
1064
|
-
:
|
|
1067
|
+
// Undated, a saved copy is not "retrieved" now: its own part says when it was saved.
|
|
1068
|
+
: (freshness.unreached || freshness.savedAt) && !freshness.asOf ? []
|
|
1065
1069
|
: [asOfText(freshness, detail.asOf === "short", local, undated)]),
|
|
1070
|
+
// A list dated by its newest filing says when it was read as well.
|
|
1071
|
+
...(freshness.retrievedToo && freshness.asOf && !freshness.savedAt ? [`retrieved ${timeText(freshness.retrievedAt, local)}`] : []),
|
|
1066
1072
|
...(freshness.schedule && detail.schedule !== "none"
|
|
1067
1073
|
? [typeof detail.schedule === "number" ? freshness.scheduleVariants?.[detail.schedule] ?? freshness.schedule : freshness.schedule] : []),
|
|
1068
1074
|
...(freshness.oldestAgeMinutes != null ? [`oldest ${ageText(freshness.oldestAgeMinutes)}`] : []),
|
|
@@ -1426,6 +1426,13 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
1426
1426
|
data: null,
|
|
1427
1427
|
bloomberg: [],
|
|
1428
1428
|
},
|
|
1429
|
+
ROADMAP: {
|
|
1430
|
+
summary: "What is being built, what is planned and what shipped, with each item's votes. Pro members vote on what is building or planned, once per item, and can take the vote back.",
|
|
1431
|
+
usage: ["ROADMAP"],
|
|
1432
|
+
keys: [OPEN, key("v", "ote")],
|
|
1433
|
+
data: null,
|
|
1434
|
+
bloomberg: [],
|
|
1435
|
+
},
|
|
1429
1436
|
HELP: {
|
|
1430
1437
|
summary: "Help and the function reference. HELP with a function opens its card, F1 opens the focused pane's, and HELP HELP reaches support.",
|
|
1431
1438
|
usage: ["HELP", "HELP OMON", "HELP HELP"],
|
|
@@ -36,8 +36,8 @@ import {
|
|
|
36
36
|
ListingArgError, listingFateLoader, listingReadNotes, loadForListing, loadListingQuoteAnswer, quotedListingChoiceNotes, refuseMovedListing,
|
|
37
37
|
} from "../listing-arg";
|
|
38
38
|
import { notATickerStop } from "../company-name-hint";
|
|
39
|
-
import { CLI_ERROR_CODE, failWithError, isCliFailure,
|
|
40
|
-
import { DATA_SERVICE_UNREACHABLE_MESSAGE } from "../../utils/network-failure";
|
|
39
|
+
import { CLI_ERROR_CODE, failUnreachable, failWithError, isCliFailure, unreachableError } from "../errors";
|
|
40
|
+
import { DATA_SERVICE_UNREACHABLE_MESSAGE, isNetworkFailure } from "../../utils/network-failure";
|
|
41
41
|
import { isTickerLinkPeer } from "../../layout/ticker-navigation";
|
|
42
42
|
import { knownGapMessage, commandPointer, commandPointerLeads, topicPointer } from "../known-gaps";
|
|
43
43
|
import { selectReportTables, type CliReportTables } from "../report-tables";
|
|
@@ -264,7 +264,7 @@ export function reportFailure(
|
|
|
264
264
|
const reasons = [...data.errors ?? [], ...typeof data.reason === "string" ? [data.reason] : []];
|
|
265
265
|
const unreached = reasons.filter((reason) => reason.includes(DATA_SERVICE_UNREACHABLE_MESSAGE));
|
|
266
266
|
if (unreached.length > 0 && (data.empty || data.unavailableSymbols.length > 0)) {
|
|
267
|
-
return
|
|
267
|
+
return unreachableError(`${asked}: ${unreached.join("; ")}.`.replace(/\.\.$/, "."));
|
|
268
268
|
}
|
|
269
269
|
if (!data.empty && !(noListing && noValues) && !(failsWithoutData && foundNothing(data))) return null;
|
|
270
270
|
const unavailable = data.unavailableSymbols.length > 0 ? `no data for ${data.unavailableSymbols.join(", ")}` : "no data";
|
|
@@ -618,6 +618,9 @@ async function runPaneCliCommand(ctx: CliCommandContext, run: () => Promise<void
|
|
|
618
618
|
// A listing it cannot use says what to type instead; a name that opens nothing, what is close to it.
|
|
619
619
|
if (error instanceof ListingArgError || error instanceof UnknownPaneFunctionError) ctx.fail(error.message, error.details || undefined);
|
|
620
620
|
const message = error instanceof Error ? error.message : String(error);
|
|
621
|
+
// Nothing answered: said as every command says it, with what to check.
|
|
622
|
+
if (message.includes(DATA_SERVICE_UNREACHABLE_MESSAGE)) failUnreachable(message);
|
|
623
|
+
if (isNetworkFailure(error)) failUnreachable();
|
|
621
624
|
// A failure the command already worded keeps its advice line.
|
|
622
625
|
const stated = error instanceof Error ? (error as Error & { details?: unknown }).details : undefined;
|
|
623
626
|
const details = typeof stated === "string" ? stated : undefined;
|