@gloom-sh/terminal 0.19.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -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/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/architecture/core-builtin-allowlist.json +2 -2
- package/src/cli/commands/market.ts +35 -12
- package/src/cli/commands/news-search.ts +22 -0
- package/src/cli/commands/overview.ts +86 -20
- package/src/cli/commands/ticker.ts +32 -2
- package/src/cli/errors.ts +17 -2
- package/src/cli/glossary.ts +6 -0
- package/src/cli/index-roots.ts +0 -7
- package/src/cli/listing-arg.ts +57 -10
- 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 +1 -1
- 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 +6 -7
- package/src/components/command-bar/selection-runtime.ts +52 -3
- package/src/components/command-bar/surface/index.tsx +2 -0
- package/src/components/layout/pane/capture-stamp.ts +2 -2
- package/src/components/layout/pane/footer/index.tsx +22 -6
- package/src/components/layout/pane/footer/model.ts +83 -15
- package/src/components/layout/pane/status-footer.ts +35 -2
- package/src/components/layout/status-widget-space.ts +9 -0
- package/src/components/ticker/list-table-view.tsx +4 -20
- package/src/components/ui/display.tsx +18 -3
- package/src/components/ui/icon.tsx +4 -1
- package/src/data/plugin-cache.ts +5 -3
- package/src/data/saved-copies.ts +8 -0
- 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/news/aggregator.ts +7 -1
- package/src/news/types.ts +6 -0
- 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/chart-composer/follow-binding.ts +14 -2
- package/src/plugins/builtin/chart-composer/index.tsx +4 -5
- 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/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/credit-documents/headless.ts +3 -3
- package/src/plugins/builtin/doe/headless.ts +3 -2
- package/src/plugins/builtin/doe/pane.tsx +13 -3
- package/src/plugins/builtin/earnings-calls/data.ts +18 -6
- package/src/plugins/builtin/earnings-calls/headless.ts +13 -3
- package/src/plugins/builtin/earnings-calls/pane.tsx +7 -0
- 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 +2 -2
- 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/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 +3 -0
- 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/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 +2 -0
- package/src/plugins/builtin/market-movers/headless.ts +3 -1
- package/src/plugins/builtin/market-movers/screener.ts +8 -4
- package/src/plugins/builtin/mna/headless.ts +2 -0
- package/src/plugins/builtin/mna/pane.tsx +3 -1
- 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/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 +4 -2
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- 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/sec/headless.ts +2 -1
- package/src/plugins/builtin/sec/index.tsx +3 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +13 -5
- package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
- 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/thirteenf/api.ts +12 -1
- package/src/plugins/builtin/thirteenf/data.ts +7 -7
- package/src/plugins/builtin/thirteenf/headless.ts +31 -4
- 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/headless.ts +21 -12
- package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
- package/src/plugins/builtin/volatility/pane.tsx +5 -2
- package/src/plugins/builtin/yield-curve/client.ts +61 -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/portfolio/column-values.ts +11 -7
- package/src/renderers/dom/desktop/icons.tsx +7 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +9 -10
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +8 -0
- 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/create.ts +43 -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/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/news-source.ts +2 -0
- package/src/renderers/electrobun/view/ui-host.tsx +8 -0
- package/src/sources/gloomberb-cloud/fred-series.ts +3 -1
- package/src/sources/gloomberb-cloud/index.ts +42 -8
- package/src/tickers/company-name-listing.ts +53 -13
- package/src/tickers/company-name.ts +6 -1
- package/src/tickers/index-roots.ts +7 -0
- package/src/tickers/listing-fate.ts +2 -2
- package/src/tickers/search/index.ts +6 -2
- 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/types/company-from.ts +40 -0
- package/src/types/data-provider.ts +7 -0
- package/src/types/financials.ts +27 -0
- package/src/types/headless.ts +6 -0
- package/src/utils/crypto-pair.ts +121 -7
- package/src/utils/declared-dividends.ts +1 -1
- package/src/utils/fundamentals.ts +36 -5
- package/src/version.ts +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logFailure } from "../../utils/log-failure";
|
|
2
|
+
import { readCompanyFrom } from "../../types/company-from";
|
|
2
3
|
import { exchangeRateMetadata } from "../../utils/exchange-rate-snapshot";
|
|
3
4
|
import type { ExchangeRateSnapshot } from "../../types/exchange-rate";
|
|
4
5
|
import type { TimeRange } from "../../time-series/range";
|
|
@@ -42,7 +43,7 @@ import {
|
|
|
42
43
|
import type { NewsArticle, NewsQuery } from "../../types/news-source";
|
|
43
44
|
import { normalizeNewsFeed } from "../../news/news-model";
|
|
44
45
|
import { resolveCurrencyUnit } from "../../utils/currency-units";
|
|
45
|
-
import { isRoundTheClockCoin } from "../../utils/crypto-pair";
|
|
46
|
+
import { exchangeLineCoinListing, isRoundTheClockCoin, withExchangeLinesAsCoinListings } from "../../utils/crypto-pair";
|
|
46
47
|
import { canonicalExchange, canonicalTickerKey, parsePublicTickerKey } from "../../utils/exchanges";
|
|
47
48
|
import { normalizePriceHistory, priceHistoryIntervalMs, reachesLatestSettledSession } from "../../utils/price-history";
|
|
48
49
|
import { createProviderMiss, ProviderBusyError, providerMissReason, StaleHistoryError } from "../provider-errors";
|
|
@@ -315,14 +316,35 @@ function cloudResponseTargetKey(key: string, requested: Set<string>): string | u
|
|
|
315
316
|
|
|
316
317
|
/**
|
|
317
318
|
* The listing as Gloom Cloud knows it. A Hong Kong code goes out with its four
|
|
318
|
-
* digits: Cloud has no 700 or 5 on HKEX, only 0700 and 0005.
|
|
319
|
+
* digits: Cloud has no 700 or 5 on HKEX, only 0700 and 0005. A crypto
|
|
320
|
+
* exchange's line of a coin pair (BTC/GBP on COINBASE PRO), which no source
|
|
321
|
+
* quotes, goes out as the crypto venue's listing of the pair, BTC-GBP on CCC.
|
|
319
322
|
*/
|
|
320
323
|
function cloudInstrumentTarget(symbol: string, exchange?: string) {
|
|
321
324
|
rejectPredictionMarket(symbol, exchange);
|
|
322
325
|
const target = publicListingTarget(symbol, exchange);
|
|
326
|
+
const coin = exchangeLineCoinListing(target.symbol, target.exchange);
|
|
327
|
+
if (coin) return { symbol: coin.symbol, exchange: coin.exchange };
|
|
323
328
|
return target.exchange ? { ...target, symbol: hongKongListingCode(target.symbol, target.exchange) } : target;
|
|
324
329
|
}
|
|
325
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Whether a quote names a listing other than the one asked for: asked for BHP
|
|
333
|
+
* on Cboe Australia, Gloom Cloud has answered with BHP on NYSE, in dollars. A
|
|
334
|
+
* price for another instrument is worse than none. A quote that names no
|
|
335
|
+
* listing is judged by its symbol alone, as the router judges it.
|
|
336
|
+
*/
|
|
337
|
+
function pricesOtherListing(quote: Quote, target: ReturnType<typeof cloudInstrumentTarget>): boolean {
|
|
338
|
+
const metadata = quoteMetadataFromQuote(quote);
|
|
339
|
+
const named = !!(metadata.listingExchangeName || parsePublicTickerKey(quote.symbol).exchange);
|
|
340
|
+
return !quoteMetadataMatchesTarget(metadata, named ? target.symbol : parsePublicTickerKey(target.symbol).symbol, named ? target.exchange : undefined);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function otherListingMiss(ticker: string, quote: Quote) {
|
|
344
|
+
const venue = quote.listingExchangeName || quote.exchangeName || parsePublicTickerKey(quote.symbol).exchange;
|
|
345
|
+
return createProviderMiss(`Cloud quoted ${quote.symbol}${venue ? ` on ${venue}` : ""} for ${ticker}`);
|
|
346
|
+
}
|
|
347
|
+
|
|
326
348
|
function cloudHistoryRecovery(
|
|
327
349
|
context: MarketDataRequestContext | undefined,
|
|
328
350
|
ticker: string, symbol: string, exchange: string, interval: string, start: Date, end: Date,
|
|
@@ -520,10 +542,12 @@ export class GloomberbCloudProvider implements AssetDataProvider {
|
|
|
520
542
|
return withCloudFallback(
|
|
521
543
|
async () => {
|
|
522
544
|
const response = await apiClient.getCloudQuote(target.symbol, target.exchange);
|
|
523
|
-
const
|
|
545
|
+
const answered = mapQuote(
|
|
524
546
|
unwrapRequiredCloudResponse(response, `Cloud quotes are unavailable for ${ticker}`),
|
|
525
547
|
response.providerMeta,
|
|
526
|
-
)
|
|
548
|
+
);
|
|
549
|
+
if (pricesOtherListing(answered, target)) throw otherListingMiss(ticker, answered);
|
|
550
|
+
const quote = retainRequestedQuoteSymbol(answered, ticker);
|
|
527
551
|
// A stale answer is the last price the service has. It stays flagged, so
|
|
528
552
|
// no freshness check passes it and only a request with no current quote
|
|
529
553
|
// from any source falls back to it.
|
|
@@ -585,7 +609,11 @@ export class GloomberbCloudProvider implements AssetDataProvider {
|
|
|
585
609
|
return { target, quote: null, error: createProviderMiss(`Cloud quotes are stale for ${target.symbol}`) };
|
|
586
610
|
}
|
|
587
611
|
if ((item.status === "success" || item.status === "partial") && item.data) {
|
|
588
|
-
const
|
|
612
|
+
const answered = mapQuote(item.data);
|
|
613
|
+
if (pricesOtherListing(answered, cloudInstrumentTarget(target.symbol, target.exchange))) {
|
|
614
|
+
return { target, quote: null, error: otherListingMiss(target.symbol, answered) };
|
|
615
|
+
}
|
|
616
|
+
const quote = retainRequestedQuoteSymbol(answered, target.symbol);
|
|
589
617
|
return { target, quote: item.stale === true ? { ...quote, stale: true } : quote };
|
|
590
618
|
}
|
|
591
619
|
return {
|
|
@@ -617,8 +645,9 @@ export class GloomberbCloudProvider implements AssetDataProvider {
|
|
|
617
645
|
// Perpetual markets are listings too, asked for beside the others. They are optional: a failed lookup leaves the
|
|
618
646
|
// other results, and a keystroke waits for them only briefly once the listings are in, unless the query names one.
|
|
619
647
|
const pending = searchPerpetualMarkets(query).catch(logFailure("sources.gloomberb-cloud.index", "Searching perpetual markets failed", [] as InstrumentSearchResult[]));
|
|
648
|
+
// A crypto exchange's line of a coin pair, which no source quotes, is offered as the crypto venue's listing.
|
|
620
649
|
const listings = await withCloudFallback(
|
|
621
|
-
() => apiClient.searchInstruments(query, context?.limit ?? 10),
|
|
650
|
+
async () => withExchangeLinesAsCoinListings(await apiClient.searchInstruments(query, context?.limit ?? 10)),
|
|
622
651
|
"Cloud search is unavailable",
|
|
623
652
|
).catch((error: unknown) => {
|
|
624
653
|
// Rate limited or overloaded: the search was not run, which is not a search that found nothing.
|
|
@@ -637,9 +666,12 @@ export class GloomberbCloudProvider implements AssetDataProvider {
|
|
|
637
666
|
const name = venue ? context?.listingName?.trim() || await this.listingName(ticker, venue) || undefined : undefined;
|
|
638
667
|
const filings = await withCloudFallback(async () => {
|
|
639
668
|
const response = await apiClient.getCloudSecFilings({ ticker, exchange, name, limit: count, offset: 0 });
|
|
640
|
-
|
|
669
|
+
// A foreign line's filings read from its company's home line say whose, each of them.
|
|
670
|
+
const companyFrom = readCompanyFrom(response.companyFrom);
|
|
671
|
+
return response.filings.map((filing) => companyFrom ? { ...mapCloudSecFiling(filing), companyFrom } : mapCloudSecFiling(filing));
|
|
641
672
|
}, `Cloud SEC filings are unavailable for ${ticker}`);
|
|
642
|
-
|
|
673
|
+
// The service matched a line read from its company's home line to the company itself.
|
|
674
|
+
if (!venue || filings.length === 0 || filings[0]!.companyFrom) return filings;
|
|
643
675
|
const symbol = parsePublicTickerKey(ticker).symbol;
|
|
644
676
|
// Without the listing's company the filings cannot be told apart from another company's.
|
|
645
677
|
if (!name) throw createProviderMiss(`SEC filings for ${symbol} could not be matched to its ${venue} listing`);
|
|
@@ -732,6 +764,7 @@ export class GloomberbCloudProvider implements AssetDataProvider {
|
|
|
732
764
|
async searchBatch(queries: string[], options: { limit?: number; signal?: AbortSignal } = {}): Promise<Map<string, InstrumentSearchResult[]> | null> {
|
|
733
765
|
const answers = await cloudSearchBatch(queries, options);
|
|
734
766
|
if (!answers) return null;
|
|
767
|
+
for (const [query, rows] of answers) answers.set(query, withExchangeLinesAsCoinListings(rows));
|
|
735
768
|
// Perpetual markets lead each answer, as `search` gives them, so a symbol in a list resolves as it does alone:
|
|
736
769
|
// one read of the board answers every query's search of it. A query that found no listing is searched again
|
|
737
770
|
// on its own, which finds them then; a board that does not answer leaves them out, as a search does.
|
|
@@ -965,6 +998,7 @@ export function createGloomberbCloudCapabilities(provider = createGloomberbCloud
|
|
|
965
998
|
articles: cloudNewsReadArticles(read, query.ticker),
|
|
966
999
|
nextCursor: read.nextCursor ?? null,
|
|
967
1000
|
access: read.access ?? null,
|
|
1001
|
+
companyFrom: readCompanyFrom(read.companyFrom),
|
|
968
1002
|
};
|
|
969
1003
|
},
|
|
970
1004
|
async fetchNews(query: NewsQuery): Promise<NewsArticle[]> {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { logFailure } from "../utils/log-failure";
|
|
2
2
|
import type { DataProvider } from "../types/data-provider";
|
|
3
|
+
import type { Quote } from "../types/financials";
|
|
3
4
|
import type { InstrumentSearchResult } from "../types/instrument";
|
|
4
5
|
import { canonicalExchange, isUsListingExchange, parsePublicTickerKey } from "../utils/exchanges";
|
|
5
6
|
import { companyNameMatch, sameCompany, shortCompanyName } from "./company-name";
|
|
@@ -12,7 +13,7 @@ import { hasNonLatinLetters } from "../utils/typed-text";
|
|
|
12
13
|
|
|
13
14
|
export interface CompanyNameListing {
|
|
14
15
|
symbol: string;
|
|
15
|
-
/** Canonical code of its exchange. */
|
|
16
|
+
/** Canonical code of its exchange; empty for a US symbol read bare (a line its company `moved` to). */
|
|
16
17
|
exchange: string;
|
|
17
18
|
/** The listing's name as search gives it. */
|
|
18
19
|
name: string;
|
|
@@ -22,8 +23,24 @@ export interface CompanyNameListing {
|
|
|
22
23
|
* run together (JPM, JPMorgan Chase & Co.), which the reader is asked about.
|
|
23
24
|
*/
|
|
24
25
|
meant?: Omit<CompanyNameListing, "meant">;
|
|
26
|
+
/**
|
|
27
|
+
* When the company's US line, which search still lists, moved to another
|
|
28
|
+
* symbol and this is the line it trades as now (GETY became GETYQ): the
|
|
29
|
+
* change in one line, "GETY became GETYQ on 2026-10-22".
|
|
30
|
+
*/
|
|
31
|
+
moved?: string;
|
|
25
32
|
}
|
|
26
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Where a listing's company trades now when it moved off the symbol (GETY
|
|
36
|
+
* became GETYQ on 2026-10-22), from the line's quote (null when it has none)
|
|
37
|
+
* and the name search gives it: the symbol it took, and the change in one
|
|
38
|
+
* line. Null when it did not move, or that cannot be told.
|
|
39
|
+
*/
|
|
40
|
+
export type MovedLineLookup = (
|
|
41
|
+
listing: { symbol: string; exchange: string; quote: Quote | null; name: string | null },
|
|
42
|
+
) => Promise<{ symbol: string; note: string } | null>;
|
|
43
|
+
|
|
27
44
|
/** Share lines that are not the company's common stock, though search may type them as such. */
|
|
28
45
|
const NOT_COMMON = /preferred|warrant|right|unit|note|bond|debenture/i;
|
|
29
46
|
const RECEIPT = /depositary|^(?:ADR|ADS|GDR|CDR|BDR)$/i;
|
|
@@ -70,7 +87,7 @@ export function isCompanyNameInput(input: string): boolean {
|
|
|
70
87
|
export async function findListingOfCompanyName(
|
|
71
88
|
dataProvider: Pick<DataProvider, "search"> & Partial<Pick<DataProvider, "getQuote">>,
|
|
72
89
|
typed: string,
|
|
73
|
-
options: { meant?: boolean; oneWord?: boolean; quoted?: boolean } = {},
|
|
90
|
+
options: { meant?: boolean; oneWord?: boolean; quoted?: boolean; movedLine?: MovedLineLookup } = {},
|
|
74
91
|
): Promise<CompanyNameListing | null> {
|
|
75
92
|
const name = typed.trim();
|
|
76
93
|
if (!name) return null;
|
|
@@ -110,8 +127,12 @@ export async function findListingOfCompanyName(
|
|
|
110
127
|
const lines = ranked.filter((listing) => listing === first || (!listing.home && !otherHomes.has(plainName(listing.result.name))
|
|
111
128
|
&& sameCompany(first.result.name, listing.result.name)
|
|
112
129
|
&& listing.rank.slice(0, NAME_RANKS).every((value, index) => value === first.rank[index])));
|
|
113
|
-
const
|
|
114
|
-
const
|
|
130
|
+
const quoted = options.quoted ? await firstQuoted(dataProvider, lines, options.movedLine) : { line: first, moved: null };
|
|
131
|
+
const best = quoted.line;
|
|
132
|
+
const found: CompanyNameListing = quoted.moved
|
|
133
|
+
// The line it moved to is a US symbol, read bare as `quote` reads one (GETYQ, which trades over the counter).
|
|
134
|
+
? { symbol: quoted.moved.symbol, exchange: "", name: best.result.name, moved: quoted.moved.note }
|
|
135
|
+
: { symbol: best.result.symbol.toUpperCase(), exchange: best.exchange, name: best.result.name };
|
|
115
136
|
const meant = best.stock && options.meant ? await stockNamedRunTogether(dataProvider, name, results) : null;
|
|
116
137
|
return meant ? { ...found, meant } : found;
|
|
117
138
|
}
|
|
@@ -127,22 +148,41 @@ interface RankedListing {
|
|
|
127
148
|
exchange: string;
|
|
128
149
|
}
|
|
129
150
|
|
|
151
|
+
/** A US exchange, or the US over-the-counter market a line moves to when it leaves one. */
|
|
152
|
+
function isUsVenue(exchange: string): boolean {
|
|
153
|
+
return isUsListingExchange(exchange) || exchange.startsWith("OTC") || exchange === "PNK";
|
|
154
|
+
}
|
|
155
|
+
|
|
130
156
|
/**
|
|
131
157
|
* The first of a company's lines, in their order, that has a quote: the first
|
|
132
158
|
* itself, or when it has none, the first of the next few that does, asked
|
|
133
|
-
* together.
|
|
134
|
-
*
|
|
159
|
+
* together. A first US line whose company moved to another symbol (GETY
|
|
160
|
+
* became GETYQ) is followed there before any other line, so a name never lands
|
|
161
|
+
* on a listing abroad that still quotes the old shares. The first stands when none has a quote,
|
|
162
|
+
* when the source is too busy to say, and when nothing here can ask for quotes.
|
|
135
163
|
*/
|
|
136
|
-
async function firstQuoted<T extends RankedListing>(
|
|
164
|
+
async function firstQuoted<T extends RankedListing>(
|
|
165
|
+
dataProvider: Partial<Pick<DataProvider, "getQuote">>,
|
|
166
|
+
lines: readonly T[],
|
|
167
|
+
movedLine?: MovedLineLookup,
|
|
168
|
+
): Promise<{ line: T; moved: { symbol: string; note: string } | null }> {
|
|
137
169
|
const first = lines[0]!;
|
|
138
|
-
|
|
139
|
-
|
|
170
|
+
const only = (line: T) => ({ line, moved: null });
|
|
171
|
+
if (!dataProvider.getQuote) return only(first);
|
|
172
|
+
const quoteOf = ({ result, exchange }: RankedListing) => dataProvider.getQuote!(result.symbol.toUpperCase(), exchange);
|
|
173
|
+
let quote: Quote | null = null;
|
|
140
174
|
try {
|
|
141
|
-
|
|
175
|
+
quote = await quoteOf(first) ?? null;
|
|
142
176
|
} catch (error) {
|
|
143
177
|
// A busy source says nothing of the listing; any other failure is a listing with no quote.
|
|
144
|
-
if (isProviderBusy(error)) return first;
|
|
178
|
+
if (isProviderBusy(error)) return only(first);
|
|
179
|
+
}
|
|
180
|
+
if (movedLine && isUsVenue(first.exchange)) {
|
|
181
|
+
const symbol = first.result.symbol.toUpperCase();
|
|
182
|
+
const moved = await movedLine({ symbol, exchange: first.exchange, quote, name: first.result.name?.trim() || null });
|
|
183
|
+
if (moved && moved.symbol.toUpperCase() !== symbol) return { line: first, moved: { ...moved, symbol: moved.symbol.toUpperCase() } };
|
|
145
184
|
}
|
|
185
|
+
if (hasPrice(quote)) return only(first);
|
|
146
186
|
const keys = new Set([listingKey(first)]);
|
|
147
187
|
const next: T[] = [];
|
|
148
188
|
for (const line of lines) {
|
|
@@ -151,11 +191,11 @@ async function firstQuoted<T extends RankedListing>(dataProvider: Partial<Pick<D
|
|
|
151
191
|
keys.add(listingKey(line));
|
|
152
192
|
next.push(line);
|
|
153
193
|
}
|
|
154
|
-
const priced = await Promise.all(next.map((line) =>
|
|
194
|
+
const priced = await Promise.all(next.map((line) => quoteOf(line).then(hasPrice).catch(() => {
|
|
155
195
|
// Intentional ignore: a line whose quote fails is a line with no quote.
|
|
156
196
|
return false;
|
|
157
197
|
})));
|
|
158
|
-
return next.find((_, index) => priced[index]) ?? first;
|
|
198
|
+
return only(next.find((_, index) => priced[index]) ?? first);
|
|
159
199
|
}
|
|
160
200
|
|
|
161
201
|
/** A name as two venues may spell it alike: "SIEMENS LTD" for Siemens Ltd. */
|
|
@@ -215,10 +215,15 @@ function nameWord(word: string, opening: boolean, domain: boolean): string {
|
|
|
215
215
|
* the way its source means it and comes back as it is.
|
|
216
216
|
*/
|
|
217
217
|
export function companyNameCase(name: string): string {
|
|
218
|
-
|
|
218
|
+
// A brand that opens in lower case before capitals (aTYR, iROBOT) is still a name in capitals: aTyr, iRobot.
|
|
219
|
+
const brand = /^\p{Ll}(?=\p{Lu}{2})/u;
|
|
220
|
+
const capitals = name.split(/\s+/).map((word) => word.replace(brand, "")).join(" ");
|
|
221
|
+
if (!/\p{Lu}/u.test(name) || capitals !== capitals.toUpperCase()) return name;
|
|
219
222
|
let words = 0;
|
|
220
223
|
return name.replace(/\S+/g, (token) => {
|
|
221
224
|
const opening = words++ === 0;
|
|
225
|
+
const lead = brand.exec(token)?.[0];
|
|
226
|
+
if (lead) return `${lead}${token.charAt(1)}${token.slice(2).toLowerCase()}`;
|
|
222
227
|
// A separator inside a word (Coca-Cola, AT&T, Amazon.com, McDonald's) cuts it into words of its own.
|
|
223
228
|
return token.split(/([-/.&,'\u2019]+)/).map((part, index, parts) => {
|
|
224
229
|
if (index % 2 === 1) return part;
|
|
@@ -45,3 +45,10 @@ export function indexRootSuggestion(symbol: string): string | null {
|
|
|
45
45
|
const root = indexRootFor(symbol);
|
|
46
46
|
return root ? `Did you mean ${root.symbol} (${root.name})?` : null;
|
|
47
47
|
}
|
|
48
|
+
|
|
49
|
+
/** Cboe's Treasury yield indices: 13-week bill, 5-, 10- and 30-year notes. Each level is a yield in percent. */
|
|
50
|
+
const YIELD_INDEX_SYMBOLS: ReadonlySet<string> = new Set(["^IRX", "^FVX", "^TNX", "^TYX"]);
|
|
51
|
+
|
|
52
|
+
export function isYieldIndexSymbol(symbol: string): boolean {
|
|
53
|
+
return YIELD_INDEX_SYMBOLS.has(symbol.trim().toUpperCase());
|
|
54
|
+
}
|
|
@@ -182,7 +182,7 @@ export function describeListingFate(
|
|
|
182
182
|
* ended long ago. Only such a line can be a company that moved away, so only
|
|
183
183
|
* its change lookup is waited for.
|
|
184
184
|
*/
|
|
185
|
-
function
|
|
185
|
+
export function lineMayHaveMoved(quote: Quote | null, now = Date.now()): boolean {
|
|
186
186
|
return !!quote && (isQuoteBeforeLatestSession(quote, now) || endedTradingDate(quote, now) != null);
|
|
187
187
|
}
|
|
188
188
|
|
|
@@ -206,7 +206,7 @@ export async function loadListingFate(
|
|
|
206
206
|
// A lookup started beside the command's request gets a short grace once that is ready, so a slow
|
|
207
207
|
// one never holds a symbol that trades. A line that stopped is waited for: only its company can
|
|
208
208
|
// have moved, and then the answer decides whether the command serves anything at all.
|
|
209
|
-
const changes = await lookup.settle(!options.changes ||
|
|
209
|
+
const changes = await lookup.settle(!options.changes || lineMayHaveMoved(quote) ? LATE_CHANGE_WAIT_MS : undefined);
|
|
210
210
|
return describeListingFate(listing, quote, changes);
|
|
211
211
|
} catch (error) {
|
|
212
212
|
logFailure("tickers-listing-fate", "Could not resolve listing fate")(error);
|
|
@@ -7,6 +7,7 @@ import { getListingSymbol, listingSuffixConflictsWithExchange, tickerHasListingS
|
|
|
7
7
|
import { parseOptionSymbol } from "../../utils/options";
|
|
8
8
|
import { resolveCurrencyUnit } from "../../utils/currency-units";
|
|
9
9
|
import { currencyPairSpelling } from "../../utils/currency-pair";
|
|
10
|
+
import { typedCoinPairListing } from "../../utils/crypto-pair";
|
|
10
11
|
import { assetClassMarketSymbol, parseAssetClassQuery } from "./asset-classes";
|
|
11
12
|
import { bareFuturesCode, futuresContractByCode } from "../../market-data/futures-board";
|
|
12
13
|
import { searchContractKey, searchInstrumentKey } from "./identity";
|
|
@@ -387,9 +388,12 @@ export function resolveTickerSearch(options: ResolveTickerSearchOptions): Promis
|
|
|
387
388
|
// JPY=X are EURUSD=X and USDJPY=X. USD-NGN and USDNGN, which a security could also be written as
|
|
388
389
|
// (MNT-USD is a coin), are the pair only when nothing is listed under those letters.
|
|
389
390
|
const pair = pickListing || future ? null : currencyPairSpelling(typed);
|
|
390
|
-
|
|
391
|
+
// A coin pair written as crypto exchanges write it (BTC/GBP, or BTC/GBP:KRAKEN) is the crypto venue's BTC-GBP,
|
|
392
|
+
// and what was typed when the venue has no such coin.
|
|
393
|
+
const coin = pickListing || future || pair || options.tickers.has(typed) ? null : typedCoinPairListing(typed);
|
|
394
|
+
const symbol = future?.symbol ?? coin?.symbol ?? (pair && !pair.ambiguous ? pair.symbol : typed);
|
|
391
395
|
const literal = resolveTickerSymbol(symbol, options, pickListing ? requested : null);
|
|
392
|
-
const alternatives = pickListing ? [] : [...dottedVenueQueries(symbol), ...pair?.ambiguous ? [pair.symbol] : []];
|
|
396
|
+
const alternatives = pickListing ? [] : [...dottedVenueQueries(symbol), ...pair?.ambiguous ? [pair.symbol] : [], ...coin ? [typed] : []];
|
|
393
397
|
// A listing suffix search does not carry (CEZ.PR) is read from its own quote, as `quote` reads it.
|
|
394
398
|
const quoted = !pickListing && tickerHasListingSuffix(symbol) && !parsePublicTickerKey(symbol).exchange;
|
|
395
399
|
// Most input has no venue code to retry: hand back the literal lookup
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
} from "./types";
|
|
5
5
|
import { getListingSymbol, tickerHasListingSuffix } from "../../sources/listing-symbols";
|
|
6
6
|
import { currencyPairCode } from "../../utils/currency-pair";
|
|
7
|
+
import { typedCoinPairListing } from "../../utils/crypto-pair";
|
|
7
8
|
import { canonicalExchange, exchangeCodeVenues, isUsListingExchange, parsePublicTickerKey, typedExchangeCode } from "../../utils/exchanges";
|
|
8
9
|
import { assetClassKeeps, assetClassMarketSymbol, instrumentClassCode, parseAssetClassQuery } from "./asset-classes";
|
|
9
10
|
import { futuresContractByCode } from "../../market-data/futures-board";
|
|
@@ -322,9 +323,12 @@ export function rankTickerSearchItems<T extends Pick<TickerSearchRankableItem, "
|
|
|
322
323
|
.map(({ item }) => getTickerSearchListingKey(item)),
|
|
323
324
|
);
|
|
324
325
|
|
|
326
|
+
// BTC/GBP reads as a currency pair's spelling, and is the crypto venue's BTC-GBP.
|
|
327
|
+
const coinListing = typedCoinPairListing(query)?.symbol;
|
|
325
328
|
const filtered = ranked.filter(({ item, textScore }) => {
|
|
326
329
|
if (textScore <= 0) return false;
|
|
327
|
-
if (isExplicitMarketSymbol(query) && !isExplicitMarketSymbol(item.symbol || item.label)
|
|
330
|
+
if (isExplicitMarketSymbol(query) && !isExplicitMarketSymbol(item.symbol || item.label)
|
|
331
|
+
&& parsePublicTickerKey(item.symbol || item.label).symbol !== coinListing) return false;
|
|
328
332
|
if (item.kind !== "search") return true;
|
|
329
333
|
return !matchedLocalListings.has(getTickerSearchListingKey(item));
|
|
330
334
|
});
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { canonicalExchange, parsePublicTickerKey, publicTickerKey } from "../../utils/exchanges";
|
|
12
12
|
import { scopedBrokerContractIdentityKey } from "../../utils/instrument-identity";
|
|
13
13
|
import { listingSuffixVenue } from "../../sources/listing-symbols";
|
|
14
|
+
import { coinPairQuoteCurrency, exchangeLineCoinListing, isCoinRowType } from "../../utils/crypto-pair";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* The key a result is saved under when its symbol is already saved for
|
|
@@ -36,6 +37,18 @@ export async function upsertTickerFromSearchResult(
|
|
|
36
37
|
result: InstrumentSearchResult,
|
|
37
38
|
options: { tickerSymbol?: string } = {},
|
|
38
39
|
): Promise<{ ticker: TickerRecord; created: boolean }> {
|
|
40
|
+
// A crypto exchange's line of a coin pair (BTC/GBP on COINBASE PRO), which no source quotes, is saved as the
|
|
41
|
+
// crypto venue's listing of the pair (BTC-GBP on CCC), in the currency the pair is quoted in.
|
|
42
|
+
const coin = result.brokerContract ? null
|
|
43
|
+
: exchangeLineCoinListing(getSearchResultSymbol(result), listingVenue(result), { coinRow: isCoinRowType(result.type) });
|
|
44
|
+
if (coin) {
|
|
45
|
+
result = { ...result, symbol: coin.symbol, exchange: coin.exchange, primaryExchange: undefined, currency: coin.currency };
|
|
46
|
+
if (options.tickerSymbol) options = { ...options, tickerSymbol: publicTickerKey(coin.symbol, coin.exchange) };
|
|
47
|
+
} else if (!result.currency && !result.brokerContract && canonicalExchange(listingVenue(result)) === "CCC") {
|
|
48
|
+
// The venue's own row of a pair (BTC-GBP) often comes without its currency, which the pair names.
|
|
49
|
+
const currency = coinPairQuoteCurrency(getSearchResultSymbol(result));
|
|
50
|
+
if (currency) result = { ...result, currency };
|
|
51
|
+
}
|
|
39
52
|
let symbol = options.tickerSymbol ?? getSearchResultSymbol(result);
|
|
40
53
|
// A vendor's suffixed symbol (GARAN.IS) is saved as the listing it is (GARAN on BIST), under the key
|
|
41
54
|
// GARAN:BIST is saved under, so one listing is one row. A key the caller names is kept as given.
|
|
@@ -3,7 +3,7 @@ import type { Quote } from "../types/financials";
|
|
|
3
3
|
import type { FormerIssuerRecord, TickerChangeHop, TickerChanges } from "../types/ticker-changes";
|
|
4
4
|
import { isCryptoPairSymbol } from "../utils/crypto-pair";
|
|
5
5
|
import { canonicalExchange, isUsListingExchange } from "../utils/exchanges";
|
|
6
|
-
import { sameCompany, shortCompanyName } from "./company-name";
|
|
6
|
+
import { companyNameCase, sameCompany, shortCompanyName } from "./company-name";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* A symbol that changed (FB became META), as data for `--json`. `moved`: the
|
|
@@ -209,7 +209,7 @@ export function movedAwayChange(change: TickerChange | null | undefined): MovedT
|
|
|
209
209
|
* look. A quote that stopped before the change, or that names the issuer that
|
|
210
210
|
* left, is that issuer's old line rather than a new listing.
|
|
211
211
|
*
|
|
212
|
-
* "FB now lists ProShares S&P 500 Dynamic Daily Buffer ETF; Meta Platforms
|
|
212
|
+
* "FB now lists ProShares S&P 500 Dynamic Daily Buffer ETF; FB was Meta Platforms before, now META (changed 2022-06-09)"
|
|
213
213
|
* "HFC became DINO on 2022-03-15"
|
|
214
214
|
* "METV was META until 2022-01-31" (data only: `tickerChangeLine` prints none)
|
|
215
215
|
*/
|
|
@@ -224,7 +224,8 @@ export function describeTickerChange(
|
|
|
224
224
|
if (moved) {
|
|
225
225
|
const [first] = moved.hops;
|
|
226
226
|
if (!first) return null;
|
|
227
|
-
|
|
227
|
+
// A name filed in capitals ("aTYR PHARMA INC") reads as it is written: aTyr Pharma.
|
|
228
|
+
const issuer = moved.name?.trim() ? shortCompanyName(companyNameCase(moved.name.trim())) : null;
|
|
228
229
|
const stillTheIssuer = !!current && (
|
|
229
230
|
(current.lastTraded != null && current.lastTraded < day(first.date))
|
|
230
231
|
|| (!!current.name && !!moved.name && sameCompany(current.name, moved.name))
|
|
@@ -234,8 +235,11 @@ export function describeTickerChange(
|
|
|
234
235
|
const when = moved.hops.length === 1
|
|
235
236
|
? `changed ${first.date}`
|
|
236
237
|
: `changed to ${first.to} on ${first.date}${laterHops(moved.hops)}`;
|
|
238
|
+
// "LIFE now lists Ethos Technologies Inc.; LIFE was aTyr Pharma before, now ATYR (changed 2024-06-05)".
|
|
237
239
|
const note = listed
|
|
238
|
-
?
|
|
240
|
+
? issuer
|
|
241
|
+
? `${symbol} now lists ${listed}; ${symbol} was ${issuer} before, now ${moved.symbol} (${when})`
|
|
242
|
+
: `${symbol} now lists ${listed}; the former ${symbol} trades as ${moved.symbol} (${when})`
|
|
239
243
|
: `${symbol} became ${first.to} on ${first.date}${laterHops(moved.hops)}`;
|
|
240
244
|
return { symbol, kind: "moved", note, changes: moved.hops, tradesAs: moved.symbol, issuer, listed };
|
|
241
245
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The listing a pane's company data comes from when it is not the listing on
|
|
3
|
+
* screen: a receipt or a foreign line (SKHY, HY9H in Frankfurt) reads its
|
|
4
|
+
* company's home line (000660 on KRX) for news, filings, holders, hiring and
|
|
5
|
+
* the other company panes. Gloom Cloud names it on those answers.
|
|
6
|
+
*/
|
|
7
|
+
export interface CompanyFrom {
|
|
8
|
+
symbol: string;
|
|
9
|
+
/** The venue's code (KRX, FWB2, NASDAQ). */
|
|
10
|
+
exchange: string;
|
|
11
|
+
/** The company's name. */
|
|
12
|
+
name?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const US_VENUES = new Set(["NASDAQ", "NYSE", "AMEX", "ARCA", "BATS", "CBOE", "IEX"]);
|
|
16
|
+
|
|
17
|
+
/** The listing as the note names it: `000660 KRX`, or `SKHY` on a US venue. */
|
|
18
|
+
export function companyFromLabel(from: CompanyFrom): string {
|
|
19
|
+
const exchange = from.exchange.trim().toUpperCase();
|
|
20
|
+
return exchange && !US_VENUES.has(exchange) ? `${from.symbol} ${exchange}` : from.symbol;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The note's listing from an answer's field, or null when the answer names none or something malformed. */
|
|
24
|
+
export function readCompanyFrom(value: unknown): CompanyFrom | null {
|
|
25
|
+
if (!value || typeof value !== "object") return null;
|
|
26
|
+
const { symbol, exchange, name } = value as Record<string, unknown>;
|
|
27
|
+
if (typeof symbol !== "string" || !symbol.trim() || typeof exchange !== "string") return null;
|
|
28
|
+
return { symbol: symbol.trim(), exchange: exchange.trim(), ...(typeof name === "string" && name.trim() ? { name: name.trim() } : {}) };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* What a report says under company data read from another listing, as the
|
|
33
|
+
* pane's footer does: "Company data from 000660 KRX (SK hynix)." Empty when
|
|
34
|
+
* the data is the listing's own, so a report's notes spread it as they are.
|
|
35
|
+
*/
|
|
36
|
+
export function companyDataNotes(from: CompanyFrom | null | undefined): string[] {
|
|
37
|
+
if (!from) return [];
|
|
38
|
+
const listing = companyFromLabel(from);
|
|
39
|
+
return [`Company data from ${listing}${from.name && from.name !== from.symbol ? ` (${from.name})` : ""}.`];
|
|
40
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CompanyFrom } from "./company-from";
|
|
1
2
|
import type { ExchangeRateSnapshot } from "./exchange-rate";
|
|
2
3
|
import type {
|
|
3
4
|
AnalystResearchData,
|
|
@@ -61,6 +62,12 @@ export interface SecFilingItem {
|
|
|
61
62
|
* series list reaches).
|
|
62
63
|
*/
|
|
63
64
|
fundSeries?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* The company's home line the filing was read from, when the listing asked
|
|
67
|
+
* is a receipt or a foreign line with no filer of its own (HY9H in
|
|
68
|
+
* Frankfurt reads SK hynix's). Absent on the listing's own filings.
|
|
69
|
+
*/
|
|
70
|
+
companyFrom?: CompanyFrom;
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
export interface SecFilingDocument {
|
package/src/types/financials.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CompanyFrom } from "./company-from";
|
|
1
2
|
import type { PerpBoardRow } from "../api-client/perps";
|
|
2
3
|
import type { PriceBasis } from "./instrument";
|
|
3
4
|
|
|
@@ -87,6 +88,26 @@ export interface DepositaryPair {
|
|
|
87
88
|
ordinary: string[];
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
/**
|
|
92
|
+
* A Buenos Aires CEDEAR's ratio from BYMA's published list, as Gloom Cloud
|
|
93
|
+
* serves it: 20 AAPL:BYMA are 1 Apple share. Its market cap is the company's,
|
|
94
|
+
* in the currency of the listing it stands for.
|
|
95
|
+
*/
|
|
96
|
+
export interface CedearRatio {
|
|
97
|
+
/** The CEDEAR's listing: AAPL:BYMA. */
|
|
98
|
+
listing: string;
|
|
99
|
+
/** CEDEARs that make `shares` of the company's shares: 20 for Apple. */
|
|
100
|
+
receipts: number;
|
|
101
|
+
/** The company's shares `receipts` CEDEARs stand for: 1 for Apple, 8 for BABA's 9 (one receipt of 8 shares). */
|
|
102
|
+
shares: number;
|
|
103
|
+
/** The company as the list names it: "Apple Inc.". */
|
|
104
|
+
company: string;
|
|
105
|
+
/** The listing it stands for, as a ticker: AAPL, VALE3.SA. */
|
|
106
|
+
underlying: string;
|
|
107
|
+
/** The date of BYMA's list. */
|
|
108
|
+
listedAt: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
90
111
|
/** How a depositary receipt relates to the ordinary shares, as Gloom Cloud states it. */
|
|
91
112
|
interface DepositaryReceiptFacts {
|
|
92
113
|
/** Whether the listing is a depositary receipt; absent when unknown. */
|
|
@@ -95,6 +116,8 @@ interface DepositaryReceiptFacts {
|
|
|
95
116
|
adrRatio?: number;
|
|
96
117
|
/** The receipt and its home lines, on the receipt's records and each home line's. */
|
|
97
118
|
depositaryPair?: DepositaryPair;
|
|
119
|
+
/** A Buenos Aires CEDEAR's ratio, on its records. */
|
|
120
|
+
cedearRatio?: CedearRatio;
|
|
98
121
|
}
|
|
99
122
|
|
|
100
123
|
export interface Quote extends DepositaryReceiptFacts {
|
|
@@ -436,6 +459,8 @@ export interface HolderData extends DepositaryReceiptFacts {
|
|
|
436
459
|
asOf?: string;
|
|
437
460
|
summary?: HolderSummary;
|
|
438
461
|
holders: HolderRecord[];
|
|
462
|
+
/** The company's home line the data is read from, when not the listing asked (a receipt, a foreign line). */
|
|
463
|
+
companyFrom?: CompanyFrom;
|
|
439
464
|
}
|
|
440
465
|
|
|
441
466
|
interface AnalystPriceTarget {
|
|
@@ -514,6 +539,8 @@ export interface DeclaredDividendAction {
|
|
|
514
539
|
declarationDate?: string;
|
|
515
540
|
recordDate?: string;
|
|
516
541
|
payDate?: string;
|
|
542
|
+
/** A one-off beside the regular payments (AIOK's $3.50), as the server marks it. */
|
|
543
|
+
special?: true;
|
|
517
544
|
}
|
|
518
545
|
|
|
519
546
|
export interface SplitAction {
|
package/src/types/headless.ts
CHANGED
|
@@ -403,6 +403,12 @@ export interface HeadlessPaneFreshness {
|
|
|
403
403
|
* `newest story` dates a news list `newest story Tue 29 Sep 2026 20:17 UTC`.
|
|
404
404
|
*/
|
|
405
405
|
asOfLabel?: string;
|
|
406
|
+
/**
|
|
407
|
+
* Say when the rows were retrieved too, after the newest one's date: a list
|
|
408
|
+
* dated by its newest filing was not read then (`newest filing Tue 27 Oct
|
|
409
|
+
* 2026 · retrieved Wed 28 Oct 2026 19:07 UTC`).
|
|
410
|
+
*/
|
|
411
|
+
retrievedToo?: boolean;
|
|
406
412
|
/** What the figures are, said after the status: `funding = current estimate, pays 08:00 UTC`. */
|
|
407
413
|
note?: string;
|
|
408
414
|
/**
|