@gloom-sh/terminal 0.19.0 → 0.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +2 -1
- package/patches/electrobun@1.18.1.patch +23 -1
- package/src/api-client/app-attention.ts +3 -0
- package/src/api-client/beneficial-owners.ts +3 -0
- package/src/api-client/company-kpis.ts +3 -0
- package/src/api-client/data.ts +2 -1
- package/src/api-client/errors.ts +38 -0
- package/src/api-client/hiring.ts +3 -0
- package/src/api-client/index.ts +4 -0
- package/src/api-client/ipo.ts +24 -1
- package/src/api-client/mna.ts +3 -0
- package/src/api-client/paths.ts +9 -1
- package/src/api-client/roadmap.ts +85 -0
- package/src/api-client/types.ts +12 -0
- package/src/app/runtime/startup.ts +3 -1
- package/src/architecture/core-builtin-allowlist.json +2 -2
- package/src/capabilities/chart-series.ts +2 -1
- package/src/cli/commands/market.ts +44 -16
- package/src/cli/commands/news-search.ts +22 -0
- package/src/cli/commands/overview.ts +84 -19
- package/src/cli/commands/search.ts +13 -13
- package/src/cli/commands/ticker.ts +32 -2
- package/src/cli/context.ts +1 -0
- package/src/cli/errors.ts +17 -2
- package/src/cli/glossary.ts +8 -1
- package/src/cli/history-rows.ts +35 -7
- package/src/cli/index-roots.ts +0 -7
- package/src/cli/known-gaps.ts +11 -0
- package/src/cli/listing-arg.ts +74 -11
- package/src/cli/pane-functions/capabilities.ts +1 -0
- package/src/cli/pane-functions/discovery.ts +21 -1
- package/src/cli/pane-functions/freshness.ts +7 -1
- package/src/cli/pane-functions/function-help.ts +7 -0
- package/src/cli/pane-functions/index.ts +6 -3
- package/src/cli/quote-answer.ts +30 -7
- package/src/cli/saved-copy-persistence.ts +88 -0
- package/src/components/command-bar/list/model.ts +5 -0
- package/src/components/command-bar/routes/root/runtime.ts +29 -8
- package/src/components/command-bar/selection-runtime.ts +64 -32
- package/src/components/command-bar/surface/index.tsx +2 -0
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/header.tsx +2 -2
- package/src/components/layout/pane/capture-stamp.ts +2 -2
- package/src/components/layout/pane/footer/index.tsx +23 -6
- package/src/components/layout/pane/footer/model.ts +116 -16
- package/src/components/layout/pane/header.tsx +2 -2
- package/src/components/layout/pane/status-footer.ts +37 -3
- package/src/components/layout/status-widget-space.ts +9 -0
- package/src/components/onboarding/onboarding-frame.tsx +2 -1
- package/src/components/onboarding/onboarding-wizard.tsx +6 -3
- package/src/components/ticker/list-table-view.tsx +4 -20
- package/src/components/ui/display.tsx +29 -3
- package/src/components/ui/icon.tsx +12 -3
- package/src/core/app-services.ts +11 -2
- package/src/data/app-persistence.ts +9 -7
- package/src/data/plugin-cache.ts +20 -3
- package/src/data/plugin-state-store.ts +81 -24
- package/src/data/resource-store.ts +130 -58
- package/src/data/saved-copies.ts +8 -0
- package/src/data/session-store.ts +31 -8
- package/src/data/sqlite/access.ts +313 -0
- package/src/data/sqlite/checkpointer.ts +198 -0
- package/src/data/sqlite/database.ts +47 -4
- package/src/data/sqlite/retry.ts +50 -9
- package/src/data/ticker-store.ts +57 -23
- package/src/docs/snapshot.json +32 -5
- package/src/i18n/es.ts +2 -4
- package/src/i18n/ja.ts +2 -4
- package/src/i18n/ko.ts +2 -4
- package/src/i18n/zh-cn.ts +2 -4
- package/src/i18n/zh-tw.ts +2 -4
- package/src/market-data/depositary-pair.ts +25 -1
- package/src/market-data/quotes/metadata.ts +7 -0
- package/src/market-data/realized-volatility.ts +4 -2
- package/src/news/aggregator.ts +7 -1
- package/src/news/types.ts +6 -0
- package/src/plugins/auto-update-node.ts +10 -6
- package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
- package/src/plugins/builtin/alerts/cli.ts +34 -11
- package/src/plugins/builtin/alerts/event-rows.ts +24 -3
- package/src/plugins/builtin/alerts/events-pane.tsx +15 -6
- package/src/plugins/builtin/alerts/headless.ts +10 -6
- package/src/plugins/builtin/cds/client.ts +142 -30
- package/src/plugins/builtin/cds/headless.ts +35 -8
- package/src/plugins/builtin/cds/model.ts +145 -18
- package/src/plugins/builtin/cds/pane.tsx +16 -5
- package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
- package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +5 -0
- package/src/plugins/builtin/chart-composer/index.tsx +4 -5
- package/src/plugins/builtin/chart-composer/pane.tsx +5 -1
- package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
- package/src/plugins/builtin/chat/controller/index.ts +5 -1
- package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
- package/src/plugins/builtin/chat/controller/state.ts +25 -2
- package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
- package/src/plugins/builtin/chat/controller/utils.ts +7 -2
- package/src/plugins/builtin/chat/status-preview.ts +79 -0
- package/src/plugins/builtin/chat/status-widget.tsx +147 -10
- package/src/plugins/builtin/chat/unread-badge.ts +51 -10
- package/src/plugins/builtin/clinical-trials/client.ts +49 -4
- package/src/plugins/builtin/clinical-trials/model.ts +86 -22
- package/src/plugins/builtin/clinical-trials/pane.tsx +46 -27
- package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +87 -9
- package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
- package/src/plugins/builtin/commodity-balances/client.ts +4 -1
- package/src/plugins/builtin/company-attention/headless.ts +2 -1
- package/src/plugins/builtin/company-attention/pane.tsx +3 -0
- package/src/plugins/builtin/company-kpis/headless.ts +8 -3
- package/src/plugins/builtin/company-kpis/model.ts +73 -5
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/composite-plugins.ts +2 -1
- package/src/plugins/builtin/cot/client.ts +28 -2
- package/src/plugins/builtin/cot/headless.ts +4 -1
- package/src/plugins/builtin/cot/index.tsx +4 -2
- package/src/plugins/builtin/cot/model.ts +58 -7
- package/src/plugins/builtin/cot/pane.tsx +6 -2
- package/src/plugins/builtin/cpi/client.ts +6 -5
- package/src/plugins/builtin/credit-boards/series.ts +3 -4
- package/src/plugins/builtin/credit-conditions/client.ts +22 -14
- package/src/plugins/builtin/credit-documents/headless.ts +3 -3
- package/src/plugins/builtin/crypto-board/client.ts +5 -5
- package/src/plugins/builtin/crypto-board/series.ts +4 -4
- package/src/plugins/builtin/debt-maturities/series.ts +3 -3
- package/src/plugins/builtin/doe/client.ts +6 -5
- package/src/plugins/builtin/doe/headless.ts +3 -2
- package/src/plugins/builtin/doe/pane.tsx +13 -3
- package/src/plugins/builtin/doe/prices.ts +2 -3
- package/src/plugins/builtin/doe/series.ts +4 -4
- package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
- package/src/plugins/builtin/earnings-calls/data.ts +37 -17
- package/src/plugins/builtin/earnings-calls/headless.ts +13 -3
- package/src/plugins/builtin/earnings-calls/pane.tsx +34 -14
- package/src/plugins/builtin/econ/calendar-model.ts +48 -1
- package/src/plugins/builtin/econ/calendar-source.ts +4 -2
- package/src/plugins/builtin/econ/detail-view.tsx +18 -4
- package/src/plugins/builtin/econ/figures.ts +48 -0
- package/src/plugins/builtin/econ/index.tsx +14 -5
- package/src/plugins/builtin/econ/print-watch.ts +3 -1
- package/src/plugins/builtin/econ-statistics/client.ts +11 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +10 -2
- package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +3 -0
- package/src/plugins/builtin/executives/data.ts +6 -1
- package/src/plugins/builtin/executives/pane.tsx +9 -21
- package/src/plugins/builtin/fear-greed/cache.ts +4 -0
- package/src/plugins/builtin/fear-greed/series.ts +6 -4
- package/src/plugins/builtin/gpu/client.ts +4 -1
- package/src/plugins/builtin/gpu/series.ts +2 -3
- package/src/plugins/builtin/help/index.tsx +61 -59
- package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
- package/src/plugins/builtin/help/table-cells.tsx +13 -2
- package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +4 -2
- package/src/plugins/builtin/holders/headless.ts +4 -2
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/holders/report-rows.ts +4 -1
- package/src/plugins/builtin/insider/headless.ts +2 -2
- package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
- package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
- package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
- package/src/plugins/builtin/jodi/client.ts +4 -1
- package/src/plugins/builtin/macro-day/headless.ts +12 -5
- package/src/plugins/builtin/macro-day/index.tsx +1 -1
- package/src/plugins/builtin/macro-day/model.ts +39 -5
- package/src/plugins/builtin/macro-day/pane.tsx +27 -19
- package/src/plugins/builtin/market-movers/client.ts +3 -0
- package/src/plugins/builtin/market-movers/headless.ts +3 -1
- package/src/plugins/builtin/market-movers/index.tsx +10 -5
- package/src/plugins/builtin/market-movers/screener.ts +27 -6
- package/src/plugins/builtin/market-movers/session-body.tsx +2 -1
- package/src/plugins/builtin/mna/headless.ts +2 -0
- package/src/plugins/builtin/mna/pane.tsx +3 -1
- package/src/plugins/builtin/money-markets/series.ts +3 -5
- package/src/plugins/builtin/news/headless.ts +3 -1
- package/src/plugins/builtin/news/index.tsx +3 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +2 -6
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +2 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +4 -6
- package/src/plugins/builtin/power/gas-client.ts +2 -1
- package/src/plugins/builtin/power/prices-client.ts +3 -2
- package/src/plugins/builtin/realized-vol/client.ts +13 -5
- package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
- package/src/plugins/builtin/realized-vol/headless.ts +8 -4
- package/src/plugins/builtin/realized-vol/model.ts +37 -6
- package/src/plugins/builtin/realized-vol/pane.tsx +9 -7
- package/src/plugins/builtin/research/event-model.ts +30 -4
- package/src/plugins/builtin/research/index.tsx +7 -3
- package/src/plugins/builtin/research/relative-valuation-headless.ts +8 -25
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +52 -15
- package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
- package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
- package/src/plugins/builtin/risk-factors/data.ts +12 -6
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/risk-factors/index.tsx +3 -1
- package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
- package/src/plugins/builtin/roadmap/index.tsx +28 -0
- package/src/plugins/builtin/roadmap/model.ts +67 -0
- package/src/plugins/builtin/roadmap/pane.tsx +244 -0
- package/src/plugins/builtin/scanner/live-headless.ts +21 -2
- package/src/plugins/builtin/scanner/lock-note.ts +26 -3
- package/src/plugins/builtin/sec/headless.ts +2 -1
- package/src/plugins/builtin/sec/index.tsx +3 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +20 -7
- package/src/plugins/builtin/shared/fred-points.ts +6 -1
- package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
- package/src/plugins/builtin/shared/report-freshness.ts +8 -0
- package/src/plugins/builtin/shared/research-cells.tsx +1 -1
- package/src/plugins/builtin/shared/series-cache.ts +4 -4
- package/src/plugins/builtin/shared/series-warning.ts +13 -4
- package/src/plugins/builtin/short-volume/series.ts +2 -3
- package/src/plugins/builtin/social-mentions/client.ts +15 -10
- package/src/plugins/builtin/social-mentions/series.ts +2 -3
- package/src/plugins/builtin/thirteenf/api.ts +15 -9
- package/src/plugins/builtin/thirteenf/data.ts +39 -11
- package/src/plugins/builtin/thirteenf/headless.ts +31 -4
- package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +21 -5
- package/src/plugins/builtin/thirteenf/types.ts +2 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +5 -2
- package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
- package/src/plugins/builtin/ticker-detail/headless.ts +59 -8
- package/src/plugins/builtin/ticker-detail/overview/model.ts +10 -2
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +11 -6
- package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
- package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
- package/src/plugins/builtin/treasury-auctions/headless.ts +24 -13
- package/src/plugins/builtin/treasury-auctions/pane.tsx +18 -6
- package/src/plugins/builtin/volatility/client.ts +43 -16
- package/src/plugins/builtin/volatility/headless.ts +5 -2
- package/src/plugins/builtin/volatility/model.ts +17 -6
- package/src/plugins/builtin/volatility/pane.tsx +11 -4
- package/src/plugins/builtin/volatility/series.ts +2 -1
- package/src/plugins/builtin/yield-curve/client.ts +59 -1
- package/src/plugins/builtin/yield-curve/headless.ts +9 -6
- package/src/plugins/builtin/yield-curve/index.tsx +34 -10
- package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/host-link.ts +1 -1
- package/src/plugins/installer.ts +50 -39
- package/src/portfolio/column-values.ts +11 -7
- package/src/renderers/dom/desktop/icons.tsx +10 -2
- package/src/renderers/electrobun/bun/desktop/backend.ts +9 -10
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +8 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +9 -1
- package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
- package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
- package/src/renderers/electrobun/bun/index.ts +26 -2
- package/src/renderers/electrobun/bun/window/attention.ts +54 -0
- package/src/renderers/electrobun/bun/window/create.ts +45 -4
- package/src/renderers/electrobun/bun/window/events.ts +20 -0
- package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
- package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
- package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
- package/src/renderers/electrobun/launcher/preload.ts +13 -0
- package/src/renderers/electrobun/launcher/windows-emulation.ts +50 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/news-source.ts +2 -0
- package/src/renderers/electrobun/view/ui-host.tsx +16 -1
- package/src/series/resolve.ts +2 -1
- package/src/series/types.ts +5 -0
- package/src/sources/gloomberb-cloud/fred-series.ts +8 -4
- package/src/sources/gloomberb-cloud/index.ts +42 -8
- package/src/telemetry/stall-reports-node.ts +13 -2
- package/src/telemetry/stall-reports.ts +189 -13
- package/src/test-support/outage.ts +8 -0
- package/src/tickers/company-name-listing.ts +110 -40
- package/src/tickers/company-name.ts +71 -2
- package/src/tickers/index-roots.ts +7 -0
- package/src/tickers/listing-fate.ts +2 -2
- package/src/tickers/search/home-listing.ts +93 -8
- package/src/tickers/search/index.ts +7 -3
- package/src/tickers/search/ranking.ts +5 -1
- package/src/tickers/search/upsert.ts +13 -0
- package/src/tickers/ticker-changes.ts +8 -4
- package/src/time-series/market.ts +5 -3
- package/src/time-series/price-comparison.ts +20 -2
- package/src/time-series/resolve.ts +20 -1
- package/src/time-series/types.ts +7 -0
- package/src/types/company-from.ts +40 -0
- package/src/types/data-provider.ts +7 -0
- package/src/types/financials.ts +45 -3
- package/src/types/headless.ts +6 -0
- package/src/ui/host.tsx +6 -1
- package/src/utils/crypto-pair.ts +121 -7
- package/src/utils/declared-dividends.ts +1 -1
- package/src/utils/financial-statements.ts +73 -16
- package/src/utils/fundamentals.ts +53 -10
- package/src/utils/lease-liabilities.ts +54 -0
- package/src/utils/main-thread-stall.ts +47 -2
- package/src/utils/perf-marks.ts +23 -11
- package/src/utils/price-history-integrity.ts +47 -1
- package/src/version.ts +2 -2
|
@@ -3,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
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PricePoint, TickerFinancials } from "../types/financials";
|
|
2
|
-
import { pricePointIntegrity, pricePointValues, priceHistoryIntegrityNotice, mergePriceHistoryIntegrity, type PriceHistoryIntegrity } from "../utils/price-history-integrity";
|
|
2
|
+
import { pricePointIntegrity, pricePointValues, pricePointWicks, priceHistoryIntegrityNotice, mergePriceHistoryIntegrity, type PriceHistoryIntegrity } from "../utils/price-history-integrity";
|
|
3
3
|
import { canonicalTimeSeriesFieldId, isFundamentalFieldId, isMarketFieldId } from "./field-catalog";
|
|
4
4
|
import { extractFundamentalSeries } from "./fundamentals";
|
|
5
5
|
import type { ChartSeriesPriceHistoryIntegrity, ResolvedSeries, SecuritySeriesSource, SeriesPeriod, TimeSeriesPoint } from "./types";
|
|
@@ -73,8 +73,10 @@ function aggregatePriceHistory(
|
|
|
73
73
|
for (const point of sorted) {
|
|
74
74
|
const key = periodKey(point.date, period);
|
|
75
75
|
const open = isFiniteNumber(point.open) ? point.open : point.close;
|
|
76
|
-
|
|
77
|
-
const
|
|
76
|
+
// A wick too far from its bar's body to have traded does not set the week's or month's range.
|
|
77
|
+
const wicks = pricePointWicks(point);
|
|
78
|
+
const high = isFiniteNumber(wicks.high) ? wicks.high : isFiniteNumber(point.high) ? Math.max(open, point.close) : point.close;
|
|
79
|
+
const low = isFiniteNumber(wicks.low) ? wicks.low : isFiniteNumber(point.low) ? Math.min(open, point.close) : point.close;
|
|
78
80
|
const current = buckets.get(key);
|
|
79
81
|
if (!current) {
|
|
80
82
|
buckets.set(key, {
|
|
@@ -63,7 +63,24 @@ export function observationDate(time: number, series: Pick<ResolvedSeries, "time
|
|
|
63
63
|
return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* The day the weekly or monthly bar holding `date` is dated by: its week's
|
|
68
|
+
* Monday or its month's 1st, as `history --from` keeps the week or month its
|
|
69
|
+
* first date falls in. A daily bar is its own day.
|
|
70
|
+
*/
|
|
71
|
+
function windowBarStart(date: string, resolution: "1d" | "1wk" | "1mo"): string {
|
|
72
|
+
if (resolution === "1mo") return `${date.slice(0, 7)}-01`;
|
|
73
|
+
if (resolution === "1d") return date;
|
|
74
|
+
const day = Date.parse(`${date}T00:00:00Z`);
|
|
75
|
+
return new Date(day - ((new Date(day).getUTCDay() + 6) % 7) * 86_400_000).toISOString().slice(0, 10);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Date-only research windows select local sessions; runtime timestamps remain
|
|
80
|
+
* exact. Weekly and monthly bars keep the bar the window starts in: a window
|
|
81
|
+
* from 2019-12-31 in monthly bars starts with December 2019's, whose close is
|
|
82
|
+
* that day's, so a comparison is measured from it.
|
|
83
|
+
*/
|
|
67
84
|
export function priceObservationWindowFilter(
|
|
68
85
|
series: ResolvedSeries,
|
|
69
86
|
bounds: { start: number | null; end: number | null },
|
|
@@ -74,11 +91,12 @@ export function priceObservationWindowFilter(
|
|
|
74
91
|
const calendarWindow = calendarBars && dateWindow
|
|
75
92
|
&& /^\d{4}-\d{2}-\d{2}$/.test(dateWindow.start)
|
|
76
93
|
&& /^\d{4}-\d{2}-\d{2}$/.test(dateWindow.end) ? dateWindow : null;
|
|
94
|
+
const start = calendarWindow ? windowBarStart(calendarWindow.start, resolution as "1d" | "1wk" | "1mo") : null;
|
|
77
95
|
return (time) => {
|
|
78
96
|
if (!Number.isFinite(time)) return false;
|
|
79
97
|
const key = calendarWindow ? observationDate(time, series) : null;
|
|
80
98
|
return key !== null
|
|
81
|
-
? key >=
|
|
99
|
+
? key >= start! && key <= calendarWindow!.end
|
|
82
100
|
: (bounds.start === null || time >= bounds.start) && (bounds.end === null || time <= bounds.end);
|
|
83
101
|
};
|
|
84
102
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logFailure } from "../utils/log-failure";
|
|
2
|
+
import { servedFromSavedCopy } from "../data/saved-copies";
|
|
2
3
|
import { hasValidQuoteObservationTime } from "../market-data/quotes/freshness";
|
|
3
4
|
import { delistingLabel } from "../market-data/quotes/delisting";
|
|
4
5
|
import { financialPeriodCoverage, financialPeriodCoverageWarnings, limitSeriesObservations } from "./financial-period-coverage";
|
|
@@ -827,7 +828,7 @@ async function resolveChartSpecPass(
|
|
|
827
828
|
cache.capabilitySeriesByRequest.set(key, pending);
|
|
828
829
|
cache.revision += 1;
|
|
829
830
|
}
|
|
830
|
-
return baseCapabilitySeries(seriesSpec, await pending, index);
|
|
831
|
+
return asSavedCopy(baseCapabilitySeries(seriesSpec, await pending, index));
|
|
831
832
|
}
|
|
832
833
|
if (seriesSpec.source.kind === "economic") {
|
|
833
834
|
const request: FredSeriesRequest = {
|
|
@@ -845,6 +846,8 @@ async function resolveChartSpecPass(
|
|
|
845
846
|
priorityWarnings.push(`${result.label}: ${coverageNotice}`);
|
|
846
847
|
coverageNotices.push(`${result.label}: ${coverageNotice}`);
|
|
847
848
|
}
|
|
849
|
+
// A saved copy that answered for a failed refresh is dated in the footer, as every pane's is.
|
|
850
|
+
if (result && fred.source === "stale-fallback") return asSavedCopy({ ...result, savedAt: fred.fetchedAt });
|
|
848
851
|
const freshnessWarning = staleFredWarning(fred);
|
|
849
852
|
if (result && freshnessWarning) priorityWarnings.push(`${result.label}: ${freshnessWarning}`);
|
|
850
853
|
return result;
|
|
@@ -1180,6 +1183,7 @@ async function resolveChartSpecPass(
|
|
|
1180
1183
|
loading: false,
|
|
1181
1184
|
errors,
|
|
1182
1185
|
warnings: [...new Set([...priorityWarnings, ...warnings])],
|
|
1186
|
+
...savedCopyOf(rawSeries.filter((entry) => visibleSeriesIds.has(entry.id))),
|
|
1183
1187
|
...(coverageNotices.length ? { coverageNotices: [...new Set(coverageNotices)] } : {}),
|
|
1184
1188
|
...(returnBasisNotice ? { returnBasisNotice } : {}),
|
|
1185
1189
|
...(delistings.size ? { delistings: [...delistings.values()] } : {}),
|
|
@@ -1188,3 +1192,18 @@ async function resolveChartSpecPass(
|
|
|
1188
1192
|
...(builtBarSize && rawSeries.some((entry) => entry.barSize === builtBarSize) ? { barSize: builtBarSize } : {}),
|
|
1189
1193
|
};
|
|
1190
1194
|
}
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* A series drawn from a copy a failed refresh left answering (`savedAt`),
|
|
1198
|
+
* marked as one, so a command charting it ends with "couldn't refresh; saved
|
|
1199
|
+
* copy from …" as the app's footer says.
|
|
1200
|
+
*/
|
|
1201
|
+
function asSavedCopy(series: ResolvedSeries): ResolvedSeries {
|
|
1202
|
+
return series.savedAt != null ? servedFromSavedCopy(series, series.savedAt) : series;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/** The oldest saved copy among `series` that answered for a failed refresh, which the chart's footer dates. */
|
|
1206
|
+
function savedCopyOf(series: readonly ResolvedSeries[]): { savedAt?: number } {
|
|
1207
|
+
const saved = series.flatMap((entry) => entry.savedAt != null && Number.isFinite(entry.savedAt) ? [entry.savedAt] : []);
|
|
1208
|
+
return saved.length ? { savedAt: Math.min(...saved) } : {};
|
|
1209
|
+
}
|
package/src/time-series/types.ts
CHANGED
|
@@ -265,6 +265,8 @@ export interface ResolvedSeries {
|
|
|
265
265
|
/** Rejected valuation price inputs, retained independently of usable observations. */
|
|
266
266
|
valuationPriceIssues?: import("./valuation-price").ValuationPriceIssue[];
|
|
267
267
|
warning?: string;
|
|
268
|
+
/** When the copy the points came from was saved (ms), when its refresh failed and that copy answered. */
|
|
269
|
+
savedAt?: number;
|
|
268
270
|
/** Listed in the legend so it can be restored, but not drawn. */
|
|
269
271
|
hidden?: boolean;
|
|
270
272
|
/**
|
|
@@ -324,6 +326,11 @@ export interface ChartResolutionResult {
|
|
|
324
326
|
loading: boolean;
|
|
325
327
|
errors: string[];
|
|
326
328
|
warnings: string[];
|
|
329
|
+
/**
|
|
330
|
+
* The oldest saved copy a shown series answered from after its refresh
|
|
331
|
+
* failed (ms), which the chart's footer dates: "saved copy from …".
|
|
332
|
+
*/
|
|
333
|
+
savedAt?: number;
|
|
327
334
|
/**
|
|
328
335
|
* Where a series' source starts after the range asked for, and why (a
|
|
329
336
|
* publisher's cap on history), as `warnings` also carries it, so a report
|
|
@@ -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 {
|
|
@@ -245,7 +268,8 @@ export interface TtmConversion {
|
|
|
245
268
|
}
|
|
246
269
|
|
|
247
270
|
/**
|
|
248
|
-
* How a served enterprise value ties to market cap + total debt - cash from the latest balance sheet
|
|
271
|
+
* How a served enterprise value ties to market cap + total debt (+ leases, `leases`) - cash from the latest balance sheet
|
|
272
|
+
* served beside it.
|
|
249
273
|
* `matches` within 5%; `differs` beyond it (or beyond 2% on the other side of the market cap from net debt), within
|
|
250
274
|
* 0.7x to 1.5x, shown labelled as the provider's; `withdrawn` outside that band, with no EV or EV/revenue; `unchecked`
|
|
251
275
|
* without a recent balance sheet carrying both lines. `computed`: an insurer's or a holding company's EV, which is
|
|
@@ -261,15 +285,24 @@ export interface EnterpriseValueCheck {
|
|
|
261
285
|
/** The market cap the EV is measured on; absent only on a not-meaningful source EV served without one. */
|
|
262
286
|
marketCap?: number;
|
|
263
287
|
totalDebt?: number;
|
|
288
|
+
/** The balance sheet's lease liabilities the bridge adds to total debt, as FA's net debt does (utils/lease-liabilities); absent when none were added. */
|
|
289
|
+
leases?: number;
|
|
264
290
|
cash?: number;
|
|
265
291
|
/** YYYY-MM-DD of the balance sheet the debt and cash come from. */
|
|
266
292
|
balanceSheetDate?: string;
|
|
267
|
-
/** marketCap + totalDebt - cash. */
|
|
293
|
+
/** marketCap + totalDebt + leases - cash. */
|
|
268
294
|
computed?: number;
|
|
269
295
|
/** `cash` is cash and equivalents alone: an insurer's or a holding company's investments stay in its EV. */
|
|
270
296
|
investmentsKept?: true;
|
|
271
|
-
/** Why the EV is not meaningful (`not-meaningful`). */
|
|
297
|
+
/** Why the EV is not meaningful (`not-meaningful`), or why a withdrawn EV no balance sheet checked is withdrawn. */
|
|
272
298
|
reason?: string;
|
|
299
|
+
/** A balance sheet in another currency than the market cap: its currency and the rate at its date
|
|
300
|
+
* (`currency` per unit of `from`) that `totalDebt`, `leases` and `cash` were converted at. */
|
|
301
|
+
fx?: { from: string; rate: number };
|
|
302
|
+
/** The provider converted its EV from the statements' currency. */
|
|
303
|
+
convertedFrom?: string;
|
|
304
|
+
/** Withdrawn because the provider applied that conversion twice: its EV over `computed` is the conversion rate. */
|
|
305
|
+
convertedTwice?: true;
|
|
273
306
|
}
|
|
274
307
|
|
|
275
308
|
/** How a trailing P/E converts the EPS it divides by, when that EPS is in another currency than the price. */
|
|
@@ -436,6 +469,8 @@ export interface HolderData extends DepositaryReceiptFacts {
|
|
|
436
469
|
asOf?: string;
|
|
437
470
|
summary?: HolderSummary;
|
|
438
471
|
holders: HolderRecord[];
|
|
472
|
+
/** The company's home line the data is read from, when not the listing asked (a receipt, a foreign line). */
|
|
473
|
+
companyFrom?: CompanyFrom;
|
|
439
474
|
}
|
|
440
475
|
|
|
441
476
|
interface AnalystPriceTarget {
|
|
@@ -514,6 +549,8 @@ export interface DeclaredDividendAction {
|
|
|
514
549
|
declarationDate?: string;
|
|
515
550
|
recordDate?: string;
|
|
516
551
|
payDate?: string;
|
|
552
|
+
/** A one-off beside the regular payments (AIOK's $3.50), as the server marks it. */
|
|
553
|
+
special?: true;
|
|
517
554
|
}
|
|
518
555
|
|
|
519
556
|
export interface SplitAction {
|
|
@@ -757,6 +794,11 @@ export interface FinancialStatement {
|
|
|
757
794
|
date: string;
|
|
758
795
|
/** Source of the fiscal period date; does not establish metric publication dates. */
|
|
759
796
|
dateSource?: "sec" | "provider";
|
|
797
|
+
/**
|
|
798
|
+
* The accounting standard the issuer reports in, from the taxonomy of its latest annual report to the SEC (10-K,
|
|
799
|
+
* 20-F or 40-F): BABA's 20-F is US GAAP, NVO's IFRS. Absent for a company that files none with the SEC.
|
|
800
|
+
*/
|
|
801
|
+
accountingStandard?: "us-gaap" | "ifrs";
|
|
760
802
|
/** Original vendor period date when independent filing evidence changes it. */
|
|
761
803
|
providerDate?: string;
|
|
762
804
|
/** Filing evidence for the period identity only, not availability of every field. */
|
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
|
/**
|
package/src/ui/host.tsx
CHANGED
|
@@ -709,7 +709,12 @@ export interface SaveTextFileRequest {
|
|
|
709
709
|
|
|
710
710
|
export interface RendererHost {
|
|
711
711
|
requestExit(): void;
|
|
712
|
-
|
|
712
|
+
/**
|
|
713
|
+
* From a press on a title bar. `press.detail` counts the clicks: where the
|
|
714
|
+
* system's title bars maximize on a double-click, the second press does that
|
|
715
|
+
* instead of moving the window.
|
|
716
|
+
*/
|
|
717
|
+
startWindowDrag?(press?: { detail?: number }): Promise<void> | void;
|
|
713
718
|
controlWindow?(action: "minimize" | "toggle-maximize" | "close"): Promise<void> | void;
|
|
714
719
|
/**
|
|
715
720
|
* Opens a link in the person's browser. Resolves false when no browser could
|
package/src/utils/crypto-pair.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { canonicalExchange, parsePublicTickerKey } from "./exchanges";
|
|
1
|
+
import { canonicalExchange, EXCHANGE_TIME_ZONES, isKnownExchangeCode, parsePublicTickerKey } from "./exchanges";
|
|
2
2
|
|
|
3
3
|
// The fiat codes a pair against USD is read as FX for, never as a coin. The
|
|
4
4
|
// list Cloud reads: it serves these pairs by the same rule, so a copy judged
|
|
@@ -9,8 +9,8 @@ const FIAT_CURRENCY_CODES = new Set([
|
|
|
9
9
|
]);
|
|
10
10
|
|
|
11
11
|
const USD_COIN_PAIR = /^([A-Z0-9]+)[-/]USD$/;
|
|
12
|
-
// The other currencies and coins the crypto venue quotes a coin in (BTC-EUR, ETH-BTC).
|
|
13
|
-
const QUOTED_COIN_PAIR = /^([A-Z0-9]+)[-/](?:USDT|USDC|EUR|GBP|BTC|ETH)$/;
|
|
12
|
+
// The other currencies and coins the crypto venue quotes a coin in (BTC-EUR, BTC-JPY, ETH-BTC).
|
|
13
|
+
const QUOTED_COIN_PAIR = /^([A-Z0-9]+)[-/](?:USDT|USDC|EUR|GBP|JPY|KRW|CAD|INR|CNY|AUD|BTC|ETH)$/;
|
|
14
14
|
|
|
15
15
|
/** A coin quoted in dollars (BTC-USD, ETH/USD). EUR-USD is spot FX, not a coin. */
|
|
16
16
|
function isUsdCoinPair(symbol: string): boolean {
|
|
@@ -35,10 +35,18 @@ export function isRoundTheClockCoin(symbol?: string, exchange?: string): boolean
|
|
|
35
35
|
const parsed = symbol ? parsePublicTickerKey(symbol) : null;
|
|
36
36
|
const venue = canonicalExchange(parsed?.exchange ?? exchange) || canonicalExchange(exchange);
|
|
37
37
|
if (venue === "CCC" || canonicalExchange(exchange) === "CCC") return true;
|
|
38
|
+
if (parsed && exchangeLineCoinListing(parsed.symbol, venue)) return true;
|
|
38
39
|
return !!parsed && (isUsdCoinPair(parsed.symbol) || (!venue && isQuotedCoinPair(parsed.symbol)));
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
const SLASH_COIN_PAIR = /^([A-Z0-9]{1,15})\/(USD|EUR|GBP|USDT|USDC|BTC)$/;
|
|
42
|
+
const SLASH_COIN_PAIR = /^([A-Z0-9]{1,15})\/(USD|EUR|GBP|JPY|KRW|CAD|INR|CNY|AUD|USDT|USDC|BTC|ETH)$/;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* What the crypto venue (CCC) quotes a coin in: BTC-GBP, BTC-KRW, LINK-ETH.
|
|
46
|
+
* It has no coin in a stablecoin (BTC-USDT) or in most other currencies
|
|
47
|
+
* (BTC-TRY, BTC-BRL).
|
|
48
|
+
*/
|
|
49
|
+
const VENUE_QUOTE_LEGS: ReadonlySet<string> = new Set(["USD", "EUR", "GBP", "JPY", "KRW", "CAD", "INR", "CNY", "AUD", "BTC", "ETH"]);
|
|
42
50
|
|
|
43
51
|
let isoCurrencyCodes: ReadonlySet<string> | null = null;
|
|
44
52
|
|
|
@@ -58,9 +66,114 @@ export function dashedCoinPair(symbol: string): string | null {
|
|
|
58
66
|
return `${match[1]}-${match[2]}`;
|
|
59
67
|
}
|
|
60
68
|
|
|
69
|
+
/**
|
|
70
|
+
* The crypto exchanges whose own lines of a coin pair search lists in a currency the crypto venue quotes coins in,
|
|
71
|
+
* and the venue itself. A slash pair saved on any other venue, or on none, is not read as a coin: on the commodity
|
|
72
|
+
* or currency venue it is spot crude, gold by the gram or a currency (WTI/USD, GAU/USD, RUB/USD).
|
|
73
|
+
*/
|
|
74
|
+
const COIN_EXCHANGES: ReadonlySet<string> = new Set([
|
|
75
|
+
"CCC", "BIGONE", "BINANCE", "BITBANK", "BITBNS", "BITFINEX", "BITFLYER", "BITHUMB", "BITMART", "BITRUE", "BITSTAMP",
|
|
76
|
+
"BTC MARKETS", "BYBIT", "CEX.IO", "COINBASE PRO", "COINONE", "CRYPTO.COM EXCHANGE", "FREIEXCHANGE", "GATE.IO", "HITBTC",
|
|
77
|
+
"HUOBI", "INDEPENDENT RESERVE", "KRAKEN", "KUCOIN", "LATOKEN", "MEXC", "MXC", "OKEX", "OKX", "POLONIEX", "SYNTHETIC",
|
|
78
|
+
"UNISWAP", "UPBIT", "WAZIRX", "XT.COM", "YOBIT", "ZAIF",
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
/** Venues whose slash pairs are commodities or currencies, never coins, whatever a row's type says. */
|
|
82
|
+
const NOT_COIN_VENUES: ReadonlySet<string> = new Set(["COMMODITY", "CCY", "FX", "FOREX", "CURRENCY", "PHYSICAL CURRENCY"]);
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Spot commodities written as a pair: gold, silver, platinum and palladium, gold and silver by the gram (GAU,
|
|
86
|
+
* XAGG), WTI, Brent (XBR) and Urals crude, and copper (HG1). Never a coin, though tokens share the letters
|
|
87
|
+
* (GAU-USD is Gamer Arena).
|
|
88
|
+
*/
|
|
89
|
+
const SPOT_COMMODITY_BASES: ReadonlySet<string> = new Set(["XAU", "GAU", "XAG", "XAGG", "XPT", "XPD", "WTI", "XBR", "URALS", "HG1"]);
|
|
90
|
+
|
|
91
|
+
/** A spot commodity's pair (WTI/USD, GAU/USD) or a pair on the commodity or currency venue, which no coin is. */
|
|
92
|
+
export function isSpotCommodityOrCurrencyPair(symbol: string, exchange?: string): boolean {
|
|
93
|
+
const parsed = parsePublicTickerKey(symbol.trim().toUpperCase());
|
|
94
|
+
const named = (parsed.exchange ?? exchange ?? "").trim().toUpperCase();
|
|
95
|
+
const base = /^([A-Z0-9]{1,15})\//.exec(parsed.symbol)?.[1];
|
|
96
|
+
return !!base && (SPOT_COMMODITY_BASES.has(base) || NOT_COIN_VENUES.has(named) || NOT_COIN_VENUES.has(canonicalExchange(named)));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The crypto venue's listing of a coin pair written with a slash (BTC/GBP); null for a currency, a spot commodity or another leg. */
|
|
100
|
+
function slashCoinPairListing(symbol: string): { symbol: string; exchange: "CCC"; currency: string } | null {
|
|
101
|
+
const match = SLASH_COIN_PAIR.exec(symbol.trim().toUpperCase());
|
|
102
|
+
if (!match) return null;
|
|
103
|
+
const [, base, quote] = match as unknown as [string, string, string];
|
|
104
|
+
if (isIsoCurrencyCode(base) || SPOT_COMMODITY_BASES.has(base) || !VENUE_QUOTE_LEGS.has(quote)) return null;
|
|
105
|
+
return { symbol: `${base}-${quote}`, exchange: "CCC", currency: quote };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* A crypto exchange's own line of a coin pair (BTC/GBP on COINBASE PRO, LINK/EUR on KRAKEN, DOGE/BTC on BINANCE)
|
|
110
|
+
* as the crypto venue's listing of it, in the currency it is quoted in: BTC-GBP on CCC, in pounds. Search offers
|
|
111
|
+
* those lines, and no source quotes them.
|
|
112
|
+
*
|
|
113
|
+
* Only a pair on a crypto exchange (COIN_EXCHANGES) is one, or a search row whose type says it is a coin
|
|
114
|
+
* (`coinRow`) on a venue that is no stock, commodity or currency venue. Null for a currency pair (EUR/USD,
|
|
115
|
+
* RUB/USD), a spot commodity (XAU/USD, WTI/USD), a pair the venue quotes in no such currency (BTC/USDT), a pair
|
|
116
|
+
* with no venue, and a slash on a stock exchange, which is a share class (BRK/B).
|
|
117
|
+
*/
|
|
118
|
+
export function exchangeLineCoinListing(
|
|
119
|
+
symbol: string,
|
|
120
|
+
exchange?: string,
|
|
121
|
+
{ coinRow = false }: { coinRow?: boolean } = {},
|
|
122
|
+
): { symbol: string; exchange: "CCC"; currency: string } | null {
|
|
123
|
+
const parsed = parsePublicTickerKey(symbol);
|
|
124
|
+
const listing = slashCoinPairListing(parsed.symbol);
|
|
125
|
+
if (!listing) return null;
|
|
126
|
+
const named = (parsed.exchange ?? exchange ?? "").trim().toUpperCase();
|
|
127
|
+
const venue = canonicalExchange(named);
|
|
128
|
+
if (COIN_EXCHANGES.has(named) || COIN_EXCHANGES.has(venue)) return listing;
|
|
129
|
+
if (!coinRow || !venue || NOT_COIN_VENUES.has(named) || NOT_COIN_VENUES.has(venue)) return null;
|
|
130
|
+
// The crypto venues trade around the clock; a venue the app knows that does not is a stock exchange.
|
|
131
|
+
return isKnownExchangeCode(venue) && EXCHANGE_TIME_ZONES[venue] !== "UTC" ? null : listing;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* A coin pair typed with a slash (btc/gbp) as the crypto venue's listing of it, BTC-GBP on CCC; with a venue
|
|
136
|
+
* (BTC/GBP:KRAKEN), as that exchange's line reads (exchangeLineCoinListing). Null for a currency pair, a spot
|
|
137
|
+
* commodity (wti/usd) and a pair the venue quotes in no such currency.
|
|
138
|
+
*/
|
|
139
|
+
export function typedCoinPairListing(query: string): { symbol: string; exchange: "CCC"; currency: string } | null {
|
|
140
|
+
const parsed = parsePublicTickerKey(query.trim().toUpperCase());
|
|
141
|
+
return parsed.exchange ? exchangeLineCoinListing(parsed.symbol, parsed.exchange) : slashCoinPairListing(parsed.symbol);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Search rows with each crypto exchange's line of a coin pair (ETH/EUR on KRAKEN, COINBASE PRO, BITSTAMP and
|
|
146
|
+
* BINANCE) as the crypto venue's one listing of it, ETH-EUR on CCC in euros, where the first line stood. A pair
|
|
147
|
+
* already offered on the venue is not offered twice. Newer servers send the listing themselves.
|
|
148
|
+
*/
|
|
149
|
+
export function withExchangeLinesAsCoinListings<T extends { symbol: string; exchange: string; type: string; currency?: string; brokerContract?: unknown }>(rows: T[]): T[] {
|
|
150
|
+
const offered = new Set(rows.filter((row) => canonicalExchange(row.exchange) === "CCC").map((row) => row.symbol.trim().toUpperCase()));
|
|
151
|
+
let changed = false;
|
|
152
|
+
const mapped = rows.flatMap((row) => {
|
|
153
|
+
const coin = !row.brokerContract && isCoinRowType(row.type) ? exchangeLineCoinListing(row.symbol, row.exchange, { coinRow: true }) : null;
|
|
154
|
+
if (!coin) return [row];
|
|
155
|
+
changed = true;
|
|
156
|
+
if (offered.has(coin.symbol)) return [];
|
|
157
|
+
offered.add(coin.symbol);
|
|
158
|
+
return [{ ...row, symbol: coin.symbol, exchange: coin.exchange, currency: row.currency || coin.currency }];
|
|
159
|
+
});
|
|
160
|
+
return changed ? mapped : rows;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Whether a search row's type says it is a coin (CRYPTOCURRENCY, Digital Currency). */
|
|
164
|
+
export function isCoinRowType(type: string | undefined): boolean {
|
|
165
|
+
return /CRYPTO|DIGITAL|COIN/i.test(type ?? "");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** The currency or coin a pair on the crypto venue is quoted in: GBP for BTC-GBP; null for anything else. */
|
|
169
|
+
export function coinPairQuoteCurrency(symbol: string): string | null {
|
|
170
|
+
const match = /^([A-Z0-9]{1,15})-([A-Z]{3})$/.exec(parsePublicTickerKey(symbol).symbol);
|
|
171
|
+
return match && !isIsoCurrencyCode(match[1]!) && VENUE_QUOTE_LEGS.has(match[2]!) ? match[2]! : null;
|
|
172
|
+
}
|
|
173
|
+
|
|
61
174
|
// A base-quote pair such as BTC-USD or SOL-EUR. Equities with a share-class dash (BRK-B) and
|
|
62
175
|
// currency pairs (EURUSD=X) do not match.
|
|
63
|
-
const CRYPTO_PAIR = /^[A-Z0-9]{1,15}-(?:USD|EUR|GBP|USDT|USDC|BTC)$/;
|
|
176
|
+
const CRYPTO_PAIR = /^[A-Z0-9]{1,15}-(?:USD|EUR|GBP|JPY|KRW|CAD|INR|CNY|AUD|USDT|USDC|BTC|ETH)$/;
|
|
64
177
|
|
|
65
178
|
/**
|
|
66
179
|
* Wider than isUsdCoinPair, for the hint under a pair that will not load: it
|
|
@@ -135,9 +248,10 @@ export const TOP_COIN_NAMES: ReadonlyMap<string, string> = new Map([
|
|
|
135
248
|
["RENDER", "render"],
|
|
136
249
|
]);
|
|
137
250
|
|
|
138
|
-
const COIN_PAIR = /^([A-Z0-9]{1,15})[-/](?:USD|EUR|GBP|USDT|USDC|BTC)$/;
|
|
251
|
+
const COIN_PAIR = /^([A-Z0-9]{1,15})[-/](?:USD|EUR|GBP|JPY|KRW|CAD|INR|CNY|AUD|USDT|USDC|BTC|ETH)$/;
|
|
139
252
|
// What a pair's listing name adds after the coin's: its quote currency, an id in brackets.
|
|
140
|
-
|
|
253
|
+
// Not ETH, which ends coins' own names (Lido Staked ETH).
|
|
254
|
+
const PAIR_NAME_SUFFIX = /(?:\s*\([^)]*\))+$|\s+(?:USD|EUR|GBP|JPY|KRW|CAD|INR|CNY|AUD|USDT|USDC|BTC|US Dollars?)$/i;
|
|
141
255
|
|
|
142
256
|
/**
|
|
143
257
|
* The coin a crypto pair trades, as stories name it: "bitcoin" for BTC-USD
|
|
@@ -34,6 +34,6 @@ export function declaredDividends(
|
|
|
34
34
|
const at = Date.parse(`${exDate}T00:00:00Z`);
|
|
35
35
|
if (paid.some((time) => Math.abs(time - at) <= SAME_DISTRIBUTION_DAYS * DAY_MS)) return [];
|
|
36
36
|
return [{ exDate, amount: row.amount, ...declarationDate ? { declarationDate } : {},
|
|
37
|
-
...recordDate ? { recordDate } : {}, ...payDate ? { payDate } : {} }];
|
|
37
|
+
...recordDate ? { recordDate } : {}, ...payDate ? { payDate } : {}, ...row.special === true ? { special: true as const } : {} }];
|
|
38
38
|
}).sort((left, right) => left.exDate.localeCompare(right.exDate));
|
|
39
39
|
}
|