gloomberb 0.18.1 → 0.18.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/package.json +1 -1
- package/src/api-client/commodity-balances.ts +6 -0
- package/src/api-client/company-kpis.ts +9 -0
- package/src/api-client/crypto-markets.ts +9 -0
- package/src/api-client/data.ts +6 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/types.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +3 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
- package/src/app/runtime/plugin-bindings.ts +6 -2
- package/src/brokers/logos.ts +66 -0
- package/src/brokers/profile-form.ts +3 -0
- package/src/brokers/remote-broker-adapter.ts +13 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/service-capabilities.ts +7 -1
- package/src/cli/base-converter.ts +60 -18
- package/src/cli/commands/market.ts +281 -154
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +100 -50
- package/src/cli/currency-hints.ts +27 -0
- package/src/cli/fx-pair.ts +41 -4
- package/src/cli/glossary.ts +57 -4
- package/src/cli/helpers.ts +36 -3
- package/src/cli/index.ts +4 -1
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +37 -2
- package/src/cli/pane-functions/catalog.ts +3 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +9 -4
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +27 -3
- package/src/cli/pane-functions/headless.ts +6 -5
- package/src/cli/pane-functions/index.ts +3 -2
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +2 -1
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/series-search.ts +2 -1
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +11 -2
- package/src/components/chart/composite/format.ts +13 -1
- package/src/components/chart/composite/level-labels.ts +30 -0
- package/src/components/chart/composite/levels.ts +17 -6
- package/src/components/chart/composite/panel-surface.tsx +58 -8
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/index.ts +2 -0
- package/src/components/layout/pane/content.tsx +16 -14
- package/src/components/layout/pane/footer/index.tsx +22 -3
- package/src/components/layout/pane/footer/model.ts +99 -4
- package/src/components/layout/pane/header-tabs.tsx +15 -1
- package/src/components/onboarding/onboarding-frame.tsx +9 -11
- package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
- package/src/components/onboarding/wizard-model.ts +10 -3
- package/src/components/ui/button.tsx +11 -4
- package/src/components/ui/choice-dialog.tsx +8 -5
- package/src/components/ui/display.tsx +11 -6
- package/src/components/ui/fields.tsx +100 -20
- package/src/components/ui/icon.tsx +6 -1
- package/src/components/ui/list-row-lines.tsx +87 -0
- package/src/components/ui/list-view.tsx +14 -0
- package/src/components/ui/logo-mark.tsx +64 -0
- package/src/components/ui/number-step.ts +54 -0
- package/src/components/ui/select-button.tsx +5 -4
- package/src/components/ui/select-field.tsx +3 -3
- package/src/components/ui/status.tsx +17 -3
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- package/src/core/state/app/instrument.ts +8 -3
- package/src/data/config/store/normalize.ts +4 -1
- package/src/data/plugin-cache.ts +32 -6
- package/src/docs/snapshot.json +5 -5
- package/src/i18n/es.ts +9 -3
- package/src/i18n/ja.ts +9 -3
- package/src/i18n/ko.ts +9 -3
- package/src/i18n/zh-cn.ts +10 -4
- package/src/i18n/zh-tw.ts +10 -4
- package/src/market-data/commodity-names.ts +80 -0
- package/src/market-data/coordinator/auxiliary.ts +3 -1
- package/src/market-data/coordinator/chart.ts +3 -1
- package/src/market-data/futures-board.ts +38 -1
- package/src/market-data/futures-contract.ts +91 -0
- package/src/market-data/fx-closes.ts +78 -1
- package/src/market-data/market/change-basis.ts +219 -0
- package/src/market-data/market/format.ts +28 -12
- package/src/market-data/market/freshness.ts +38 -0
- package/src/market-data/market/status.ts +55 -9
- package/src/market-data/market/year-to-date.ts +46 -0
- package/src/market-data/quotes/contributions.ts +12 -0
- package/src/market-data/quotes/delisting.ts +34 -0
- package/src/market-data/quotes/live-valuation.ts +23 -0
- package/src/market-data/quotes/resolution.ts +8 -2
- package/src/market-data/tax-equivalent.ts +10 -2
- package/src/notifications/app-notifier.ts +2 -2
- package/src/notifications/notification-log.ts +8 -6
- package/src/plugins/builtin/alerts/cli.ts +26 -18
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- package/src/plugins/builtin/backtest/window.ts +16 -3
- package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
- package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
- package/src/plugins/builtin/broker-manager/index.tsx +20 -9
- package/src/plugins/builtin/broker-manager/model.ts +5 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
- package/src/plugins/builtin/broker-manager/table.ts +5 -1
- package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
- package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
- package/src/plugins/builtin/broker-trading/command.ts +103 -0
- package/src/plugins/builtin/broker-trading/controller.ts +688 -0
- package/src/plugins/builtin/broker-trading/dock.tsx +182 -0
- package/src/plugins/builtin/broker-trading/headless.ts +76 -0
- package/src/plugins/builtin/broker-trading/index.tsx +77 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
- package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
- package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
- package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
- package/src/plugins/builtin/broker-trading/pane.tsx +505 -0
- package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
- package/src/plugins/builtin/broker-trading/ticket.tsx +573 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
- package/src/plugins/builtin/buildout/format.ts +1 -0
- package/src/plugins/builtin/buildout/model/index.ts +16 -7
- package/src/plugins/builtin/buildout/pane/index.tsx +3 -2
- package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
- package/src/plugins/builtin/chart-composer/headless.ts +2 -1
- package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +31 -11
- package/src/plugins/builtin/chart-composer/presets.ts +2 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
- package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
- package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
- package/src/plugins/builtin/commodity-balances/client.ts +6 -4
- package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
- package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
- package/src/plugins/builtin/commodity-balances/model.ts +22 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +36 -5
- package/src/plugins/builtin/company-kpis/client.ts +3 -2
- package/src/plugins/builtin/company-kpis/headless.ts +5 -7
- package/src/plugins/builtin/company-kpis/model.ts +85 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +26 -12
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cot/index.tsx +1 -1
- package/src/plugins/builtin/cot/model.ts +35 -12
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
- package/src/plugins/builtin/crypto-board/client.ts +33 -2
- package/src/plugins/builtin/crypto-board/headless.ts +5 -2
- package/src/plugins/builtin/crypto-board/model.ts +28 -8
- package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
- package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
- package/src/plugins/builtin/econ/calendar-model.ts +35 -0
- package/src/plugins/builtin/econ/index.tsx +31 -4
- package/src/plugins/builtin/econ/print-watch.ts +279 -0
- package/src/plugins/builtin/equity-screener/headless.ts +20 -5
- package/src/plugins/builtin/equity-screener/model.ts +40 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +24 -21
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
- package/src/plugins/builtin/exposure/client.ts +4 -1
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/exposure/evidence.ts +4 -1
- package/src/plugins/builtin/exposure/holdings.ts +3 -1
- package/src/plugins/builtin/exposure/pane.tsx +37 -16
- package/src/plugins/builtin/filing-events/index.tsx +9 -3
- package/src/plugins/builtin/futures/table.tsx +6 -3
- package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
- package/src/plugins/builtin/insider/base-values.ts +72 -0
- package/src/plugins/builtin/insider/headless.ts +29 -4
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- package/src/plugins/builtin/market-halts/model.ts +31 -6
- package/src/plugins/builtin/market-halts/pane.tsx +3 -0
- package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/view.tsx +77 -22
- package/src/plugins/builtin/options-calculator/pane.tsx +4 -2
- package/src/plugins/builtin/options-scenario/pane.tsx +41 -6
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/render.ts +34 -10
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- package/src/plugins/builtin/primary-dealers/client.ts +137 -62
- package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
- package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
- package/src/plugins/builtin/primary-dealers/model.ts +179 -129
- package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
- package/src/plugins/builtin/research/analyst-model.ts +5 -1
- package/src/plugins/builtin/risk-factors/index.tsx +12 -3
- package/src/plugins/builtin/scanner/feed.ts +6 -1
- package/src/plugins/builtin/scanner/headless.ts +1 -1
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
- package/src/plugins/builtin/shared/free-routes.ts +24 -0
- package/src/plugins/builtin/shared/headless-options.ts +11 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +62 -0
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -2
- package/src/plugins/builtin/themes/model.ts +43 -10
- package/src/plugins/builtin/themes/pane.tsx +8 -6
- package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
- package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
- package/src/plugins/builtin/time-sales/pane.tsx +2 -2
- package/src/plugins/builtin/trace-bonds/client.ts +14 -3
- package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +2 -2
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/compat.ts +3 -0
- package/src/plugins/external-runtime.ts +5 -0
- package/src/plugins/loader.ts +6 -0
- package/src/plugins/plugin-icon.ts +61 -0
- package/src/plugins/registry/host-actions.ts +2 -1
- package/src/plugins/registry/index.ts +9 -1
- package/src/plugins/runtime/context.tsx +5 -0
- package/src/plugins/runtime/index.tsx +3 -0
- package/src/portfolio/column-values.ts +13 -4
- package/src/portfolio/cost-fx.ts +5 -28
- package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
- package/src/public/broker.ts +8 -0
- package/src/public/market-data.ts +5 -0
- package/src/renderers/dom/desktop/control-styles.ts +7 -1
- package/src/renderers/dom/desktop/controls.tsx +39 -7
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/list-view.tsx +51 -19
- package/src/renderers/dom/desktop/menu.tsx +10 -1
- package/src/renderers/dom/desktop/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +6 -0
- package/src/renderers/dom/styles.css +12 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +12 -0
- package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
- package/src/renderers/electrobun/view/external-plugins.ts +1 -0
- package/src/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/index.ts +50 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/provider-errors.ts +3 -1
- package/src/sources/provider-router/history.ts +49 -6
- package/src/sources/provider-router/index.ts +2 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +62 -11
- package/src/sync/local-only.ts +121 -0
- package/src/sync/react.ts +6 -0
- package/src/sync/types.ts +3 -0
- package/src/tickers/listing-fate.ts +27 -8
- package/src/tickers/search/index.ts +24 -3
- package/src/time-series/price-comparison.ts +35 -0
- package/src/time-series/resolve.ts +52 -11
- package/src/time-series/spec.ts +1 -1
- package/src/time-series/types.ts +5 -0
- package/src/types/broker.ts +56 -1
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +5 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +12 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +2 -1
- package/src/utils/currency-units.ts +32 -1
- package/src/utils/dom-screenshot.ts +22 -9
- package/src/utils/exchange-notation.generated.ts +360 -0
- package/src/utils/exchanges.ts +15 -322
- package/src/utils/format.ts +31 -1
- package/src/utils/instrument-identity.ts +38 -1
- package/src/utils/options.ts +46 -1
- package/src/utils/price-history.ts +4 -0
- package/src/utils/reported-money.ts +8 -4
package/src/sync/types.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Dispatch } from "react";
|
|
2
2
|
import type { AppAction, AppState } from "../core/state/app/state";
|
|
3
3
|
import type { AppTickerRepositoryPort } from "../core/app-service-ports";
|
|
4
|
+
import type { LocalMarketDataLookup } from "./local-only";
|
|
4
5
|
|
|
5
6
|
export const SYNC_SNAPSHOT_SCHEMA_VERSION = 1;
|
|
6
7
|
|
|
@@ -31,6 +32,8 @@ interface SyncContributorMemo {
|
|
|
31
32
|
|
|
32
33
|
interface SyncCollectContext {
|
|
33
34
|
state: AppState;
|
|
35
|
+
/** Sources whose quotes and bars never leave this device (`BrokerAdapter.localMarketData`); none when absent. */
|
|
36
|
+
isLocalMarketData?: LocalMarketDataLookup;
|
|
34
37
|
/** The payload this device last pushed for this contributor, or null when it never synced. */
|
|
35
38
|
baselinePayload?: unknown;
|
|
36
39
|
memo?: SyncContributorMemo;
|
|
@@ -19,6 +19,7 @@ import { isQuoteBeforeLatestSession, isQuoteStaleForCurrentSession } from "../ma
|
|
|
19
19
|
import { formatMarketPriceWithCurrency, quoteFormatOptions } from "../market-data/market/format";
|
|
20
20
|
import { canonicalExchange, isUsListingExchange, parsePublicTickerKey, resolveExchangeTimeZone } from "../utils/exchanges";
|
|
21
21
|
import { zonedDateKey } from "../utils/zoned-date-time";
|
|
22
|
+
import { delistingLabel } from "../market-data/quotes/delisting";
|
|
22
23
|
|
|
23
24
|
/*
|
|
24
25
|
* What became of a symbol, read the way `quote` reads it: the listing's own
|
|
@@ -33,8 +34,10 @@ const ENDED_AFTER_MS = 183 * 24 * 60 * 60_000;
|
|
|
33
34
|
/**
|
|
34
35
|
* The exchange-local date a listing last traded, when its quote is stale and
|
|
35
36
|
* that was over half a year ago (TWTR's 2022-10-27); null for anything newer.
|
|
37
|
+
* A quote that says its listing no longer trades gives its last session.
|
|
36
38
|
*/
|
|
37
39
|
function endedTradingDate(quote: Quote, now = Date.now()): string | null {
|
|
40
|
+
if (quote.delisting) return quote.delisting.lastTradeDate;
|
|
38
41
|
const last = Math.max(...[quote.lastTradeTime, quote.lastUpdated].filter((time): time is number => Number.isFinite(time)));
|
|
39
42
|
if (!(last > 0) || now - last < ENDED_AFTER_MS || !isQuoteStaleForCurrentSession(quote, now)) return null;
|
|
40
43
|
return quoteDateKey(quote, last);
|
|
@@ -60,12 +63,18 @@ export function tradingListing(quote: Quote | null | undefined, now = Date.now()
|
|
|
60
63
|
return quote && !endedTradingDate(quote, now) ? quotedListing(quote) : null;
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* "TWTR: no longer trading since 2022-10-27 (last price $53.86)", for a quote
|
|
68
|
+
* that stopped long ago; "TWTR: Delisted Oct 27, 2022 (last price $53.86)"
|
|
69
|
+
* when the quote says the listing no longer trades.
|
|
70
|
+
*/
|
|
64
71
|
export function noLongerTradingNote(label: string, quote: Quote, now = Date.now()): string | null {
|
|
65
72
|
const date = endedTradingDate(quote, now);
|
|
66
73
|
if (!date) return null;
|
|
67
74
|
const price = formatMarketPriceWithCurrency(quote.price, quote.currency, quoteFormatOptions(quote));
|
|
68
|
-
return
|
|
75
|
+
return quote.delisting
|
|
76
|
+
? `${label}: ${delistingLabel(quote.delisting)} (last price ${price})`
|
|
77
|
+
: `${label}: no longer trading since ${date} (last price ${price})`;
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
/**
|
|
@@ -81,11 +90,17 @@ export interface ListingFate {
|
|
|
81
90
|
/**
|
|
82
91
|
* The line a command with nothing to serve for the symbol says: `quote`'s
|
|
83
92
|
* change line when its company left it ("FB now lists ...; Meta Platforms
|
|
84
|
-
* trades as META (changed 2022-06-09)"), else, for a listing
|
|
85
|
-
* "TWTR
|
|
86
|
-
*
|
|
93
|
+
* trades as META (changed 2022-06-09)"), else, for a listing the service
|
|
94
|
+
* says no longer trades, "TWTR: Delisted Oct 27, 2022", and for another
|
|
95
|
+
* that stopped, "XYZ stopped trading 2022-10-27; no history or options
|
|
96
|
+
* served". Null for a symbol that trades as it always has.
|
|
87
97
|
*/
|
|
88
98
|
note: string | null;
|
|
99
|
+
/**
|
|
100
|
+
* "TWTR: Delisted Oct 27, 2022" when the listing's quote says it no longer
|
|
101
|
+
* trades: its history is served, ending on that session, and says so.
|
|
102
|
+
*/
|
|
103
|
+
delisted?: string;
|
|
89
104
|
/**
|
|
90
105
|
* The change when the symbol's company trades under another symbol now
|
|
91
106
|
* and its own quote shows nothing new listed under it (`movedAwayChange`):
|
|
@@ -156,9 +171,10 @@ export function describeListingFate(
|
|
|
156
171
|
const abroad = !!quote && !!bareListingNote({ symbol, request: { exchange: listing.exchange ?? "" } }, quote);
|
|
157
172
|
const current = tradingListing(quote, now);
|
|
158
173
|
const change = abroad ? null : describeTickerChange(symbol, changes, current);
|
|
159
|
-
const
|
|
174
|
+
const delisted = quote?.delisting && !abroad ? `${symbol}: ${delistingLabel(quote.delisting)}` : undefined;
|
|
175
|
+
const note = tickerChangeLine(change) ?? delisted ?? (endedOn ? `${symbol} stopped trading ${endedOn}; no history or options served` : null);
|
|
160
176
|
const formerIssuer = abroad ? null : describeFormerIssuer(symbol, changes, current);
|
|
161
|
-
return { endedOn, change, note, moved: quote ? movedAwayChange(change) : null, formerIssuer };
|
|
177
|
+
return { endedOn, change, note, moved: quote ? movedAwayChange(change) : null, formerIssuer, ...(delisted ? { delisted } : {}) };
|
|
162
178
|
}
|
|
163
179
|
|
|
164
180
|
/**
|
|
@@ -200,7 +216,8 @@ export async function loadListingFate(
|
|
|
200
216
|
|
|
201
217
|
/**
|
|
202
218
|
* The lines a command says about a symbol's fate beside what it served. With
|
|
203
|
-
* nothing served, `fate.note`. With data, `
|
|
219
|
+
* nothing served, `fate.note`. With data, `fate.delisted` for a listing that
|
|
220
|
+
* no longer trades ("TWTR: Delisted Oct 27, 2022"), `quote`'s change line when the
|
|
204
221
|
* symbol's company left it (FB's bars are the fund listed there now), and, for
|
|
205
222
|
* bars from before the symbol's holder took it (META's from 2021), that they
|
|
206
223
|
* are from its old symbol: "Includes FB-era bars: META was FB until 2022-06-09".
|
|
@@ -211,6 +228,8 @@ export function listingFateNotes(fate: ListingFate, served: { served: boolean; f
|
|
|
211
228
|
const first = served.firstDate?.slice(0, 10);
|
|
212
229
|
const earlier = change?.kind === "formerly" && first ? change.changes.filter((hop) => first < hop.date).map((hop) => hop.from) : [];
|
|
213
230
|
return [
|
|
231
|
+
// What it served ends on the listing's last session, and says so.
|
|
232
|
+
fate.delisted ?? null,
|
|
214
233
|
tickerChangeLine(change),
|
|
215
234
|
earlier.length > 0 ? `Includes ${earlier.join("- and ")}-era bars: ${change!.note}` : null,
|
|
216
235
|
].filter((line): line is string => !!line);
|
|
@@ -38,6 +38,7 @@ import { readTerminalListingCode } from "../terminal-codes";
|
|
|
38
38
|
import { isProviderBusy, ProviderBusyError, SEARCH_BUSY_MESSAGE } from "../../sources/provider-errors";
|
|
39
39
|
import { settleWithin } from "../../utils/async-deadline";
|
|
40
40
|
import { companyNameExtraWords } from "../company-name";
|
|
41
|
+
import { delistingLabel } from "../../market-data/quotes/delisting";
|
|
41
42
|
export type {
|
|
42
43
|
ResolvedTickerSearch,
|
|
43
44
|
TickerSearchCandidate,
|
|
@@ -58,6 +59,17 @@ const MAX_NAMED_LISTINGS = 6;
|
|
|
58
59
|
*/
|
|
59
60
|
export const LISTING_SEARCH_LIMIT = 30;
|
|
60
61
|
|
|
62
|
+
/**
|
|
63
|
+
* A listing's name as a choice between listings gives it: a listing that no
|
|
64
|
+
* longer trades says so and when ("First Republic Bank · Last traded Apr 28,
|
|
65
|
+
* 2023"), and drops a name that only repeats its symbol.
|
|
66
|
+
*/
|
|
67
|
+
function listingChoiceName(result: InstrumentSearchResult): string {
|
|
68
|
+
if (!result.delisting) return result.name;
|
|
69
|
+
const name = result.name && normalizeTickerSymbol(result.name) !== normalizeTickerSymbol(result.symbol) ? result.name : "";
|
|
70
|
+
return [name, delistingLabel(result.delisting)].filter(Boolean).join(" · ");
|
|
71
|
+
}
|
|
72
|
+
|
|
61
73
|
export class AmbiguousTickerError extends Error {
|
|
62
74
|
constructor(readonly query: string, readonly listings: readonly string[], readonly listingNames: Readonly<Record<string, string>> = {}) {
|
|
63
75
|
// Every listing is a choice: a list cut short could leave out the one meant.
|
|
@@ -467,7 +479,7 @@ async function resolveTickerSymbol(
|
|
|
467
479
|
}
|
|
468
480
|
const listings = new Set(matches.map((item) => publicTickerKey(item.symbol, listingExchange(item.result!))));
|
|
469
481
|
const ambiguity = (query: string) => new AmbiguousTickerError(query, [...listings], Object.fromEntries(matches.map((item) => [
|
|
470
|
-
publicTickerKey(item.symbol, listingExchange(item.result!)), item.result
|
|
482
|
+
publicTickerKey(item.symbol, listingExchange(item.result!)), listingChoiceName(item.result!),
|
|
471
483
|
])));
|
|
472
484
|
if (pickerQuery && listings.size > 1) throw ambiguity(pickerQuery);
|
|
473
485
|
// A code that names several venues (TSE) the symbol trades on more than one of is a choice.
|
|
@@ -485,7 +497,12 @@ async function resolveTickerSymbol(
|
|
|
485
497
|
});
|
|
486
498
|
const exchange = canonicalExchange(quote.listingExchangeName || quote.exchangeName);
|
|
487
499
|
const currency = resolveCurrencyUnit(quote.currency).currency;
|
|
488
|
-
|
|
500
|
+
const ended = quote.delisting;
|
|
501
|
+
if (ended) {
|
|
502
|
+
// The symbol's own quote is a US listing that no longer trades (a bare FRC, whose
|
|
503
|
+
// venue is not on record): it is the run search offers with the same last session.
|
|
504
|
+
verified = matches.filter((item) => item.result?.delisting?.lastTradeDate === ended.lastTradeDate);
|
|
505
|
+
} else if (exchange && currency && Number.isFinite(quote.price) && quote.price !== 0
|
|
489
506
|
&& Number.isFinite(quote.lastUpdated) && quote.lastUpdated > 0) {
|
|
490
507
|
const sameSecurity = matches.filter((item) => {
|
|
491
508
|
const result = item.result!;
|
|
@@ -663,7 +680,11 @@ async function searchProviderResults(
|
|
|
663
680
|
|
|
664
681
|
// A sector word ("defense", which the server answers with rows tagged with it) lists its small companies by
|
|
665
682
|
// name; the server orders "defense ETF" by size, which a name search does not. Those funds lead the list.
|
|
666
|
-
|
|
683
|
+
// So do they when the funds are asked for by name: "defense etf" searches "defense", whose answer holds
|
|
684
|
+
// only the few funds tagged with it (not ITA), and "gold miners etf" a phrase no symbol is.
|
|
685
|
+
const fundsAsked = assetClass?.code === "ETF" || assetClass?.code === "FUND";
|
|
686
|
+
const theme = withThemeFunds && (!assetClass || fundsAsked)
|
|
687
|
+
&& (themeQueryWord([...byKey.values()], query) || (fundsAsked && /\s/.test(query.trim())));
|
|
667
688
|
if (theme) {
|
|
668
689
|
try {
|
|
669
690
|
const { onPartial: _ignored, ...context } = searchContext ?? {};
|
|
@@ -123,6 +123,41 @@ export function resolvePriceComparison(
|
|
|
123
123
|
};
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* The bars a compared leg pans and zooms over: inside the view only its shared
|
|
128
|
+
* dates, as the chart shows them, and every loaded bar past either edge of the
|
|
129
|
+
* view. Clipping the whole buffer to the view left a pan or a zoom out nothing
|
|
130
|
+
* to move into until the next fetch, and none at all toward newer bars.
|
|
131
|
+
*/
|
|
132
|
+
export function comparedBufferPoints(
|
|
133
|
+
points: readonly TimeSeriesPoint[],
|
|
134
|
+
shared: { start: number | null; end: number | null },
|
|
135
|
+
view: { start: number | null; end: number | null },
|
|
136
|
+
): TimeSeriesPoint[] {
|
|
137
|
+
const { start, end } = shared;
|
|
138
|
+
if (start === null || end === null) return [];
|
|
139
|
+
const before = view.start === null ? Number.NEGATIVE_INFINITY : Math.min(start, view.start);
|
|
140
|
+
const after = view.end === null ? Number.POSITIVE_INFINITY : Math.max(end, view.end);
|
|
141
|
+
return points.filter((point) => {
|
|
142
|
+
const time = point.date.getTime();
|
|
143
|
+
return time < before || time > after || (time >= start && time <= end);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** A compared leg's pan buffer around `view`; see comparedBufferPoints. */
|
|
148
|
+
export function bufferPriceComparison(
|
|
149
|
+
series: ResolvedSeries,
|
|
150
|
+
comparison: PriceComparison | null,
|
|
151
|
+
view: { start: number | null; end: number | null },
|
|
152
|
+
): ResolvedSeries {
|
|
153
|
+
if (!comparison?.seriesIds.includes(series.id)) return series;
|
|
154
|
+
return {
|
|
155
|
+
...series,
|
|
156
|
+
latestChangePercent: undefined,
|
|
157
|
+
points: comparedBufferPoints(series.points, priceComparisonBoundsForSeries(series, comparison)!, view),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
126
161
|
/** Clip only compared legs; raw observations and study inputs stay available in the source cache. */
|
|
127
162
|
export function clipPriceComparison(series: ResolvedSeries, comparison: PriceComparison | null): ResolvedSeries {
|
|
128
163
|
if (!comparison?.seriesIds.includes(series.id)) return series;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { logFailure } from "../utils/log-failure";
|
|
2
2
|
import { resolveAssetDisplayKind } from "../market-data/market/format";
|
|
3
3
|
import { hasValidQuoteObservationTime } from "../market-data/quotes/freshness";
|
|
4
|
+
import { delistingLabel } from "../market-data/quotes/delisting";
|
|
4
5
|
import { SnapshotHistoryUnavailableError } from "../market-data/snapshot-provider";
|
|
5
6
|
import { financialPeriodCoverage, financialPeriodCoverageWarnings, limitSeriesObservations } from "./financial-period-coverage";
|
|
6
7
|
import { HistoryCoverageError, historyCoverageNotice } from "../sources/history-coverage";
|
|
@@ -10,6 +11,7 @@ import { getRouterEntityKey } from "../sources/provider-router/cache";
|
|
|
10
11
|
import { publicListingTarget } from "../sources/listing-target";
|
|
11
12
|
import { fetchHistoryResult } from "../sources/history-result";
|
|
12
13
|
import type { HistorySession, PriceHistoryResult } from "../types/price-history";
|
|
14
|
+
import type { BrokerContractRef } from "../types/instrument";
|
|
13
15
|
import { FINANCIAL_VINTAGE_NOTICE, SEC_EPS_BASIS_NOTICE } from "../utils/financial-statements";
|
|
14
16
|
import { appendLiveQuotePoint, hasUnknownBondHistoryBasis } from "./chart-data";
|
|
15
17
|
import { LiveBarAccumulator } from "./live-bars";
|
|
@@ -63,7 +65,7 @@ import {
|
|
|
63
65
|
} from "./studies";
|
|
64
66
|
import { applyResolvedSeriesTransform } from "./transforms";
|
|
65
67
|
import { clipSeriesToWindow } from "./alignment";
|
|
66
|
-
import { clipPriceComparison, priceComparisonBoundsForSeries, priceComparisonSeriesIds, priceObservationWindowFilter, resolvePriceComparison, type PriceComparison } from "./price-comparison";
|
|
68
|
+
import { bufferPriceComparison, clipPriceComparison, comparedBufferPoints, priceComparisonBoundsForSeries, priceComparisonSeriesIds, priceObservationWindowFilter, resolvePriceComparison, type PriceComparison } from "./price-comparison";
|
|
67
69
|
import { reportingCurrencySeries } from "./reporting-currency";
|
|
68
70
|
import { chartQuoteOverrideKeyForSource, compareChartQuoteRecency } from "./live-quotes";
|
|
69
71
|
import { chartSeriesSourceKey } from "../capabilities/chart-series";
|
|
@@ -754,8 +756,8 @@ export function seedChartResolutionResult(
|
|
|
754
756
|
const axisWarnings: string[] = [];
|
|
755
757
|
const bufferedSeries = assignAxes([
|
|
756
758
|
...baseSeries,
|
|
757
|
-
...applyStudyPresentationTransforms(studies.series, spec.studies, series, comparisonBounds, undefined, priceComparison),
|
|
758
|
-
], [...spec.series, ...spec.studies], axisWarnings).map((entry) =>
|
|
759
|
+
...applyStudyPresentationTransforms(studies.series, spec.studies, series, comparisonBounds, undefined, priceComparison, bounds),
|
|
760
|
+
], [...spec.series, ...spec.studies], axisWarnings).map((entry) => bufferPriceComparison(entry, priceComparison, bounds));
|
|
759
761
|
const visible = bufferedSeries.map((entry) => {
|
|
760
762
|
const entryBounds = presentationBounds(entry, spec.studies, priceComparison, bounds);
|
|
761
763
|
const clipped = entryBounds.start !== null && entryBounds.end !== null
|
|
@@ -1168,6 +1170,17 @@ async function loadPriceHistory(
|
|
|
1168
1170
|
return { ...result, expiresAt: retryAt ?? Date.now() + HISTORY_RETENTION_MAX_AGE_MS };
|
|
1169
1171
|
}
|
|
1170
1172
|
|
|
1173
|
+
/**
|
|
1174
|
+
* A US stock or fund listing, including one a broker contract routes through
|
|
1175
|
+
* SMART in dollars. Options and futures have no pre-market session.
|
|
1176
|
+
*/
|
|
1177
|
+
function tradesUsExtendedHours(exchange: string | undefined, contract: BrokerContractRef | null | undefined): boolean {
|
|
1178
|
+
if (contract?.secType && !["STK", "ETF"].includes(contract.secType.toUpperCase())) return false;
|
|
1179
|
+
const venue = exchange || contract?.primaryExchange || contract?.exchange;
|
|
1180
|
+
return isUsListingExchange(venue) || isUsListingExchange(contract?.primaryExchange)
|
|
1181
|
+
|| (!!contract && canonicalExchange(venue) === "SMART" && (!contract.currency || contract.currency.toUpperCase() === "USD"));
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1171
1184
|
function baseSecuritySeries(
|
|
1172
1185
|
spec: ChartSeriesSpec,
|
|
1173
1186
|
financials: TickerFinancials,
|
|
@@ -1185,7 +1198,8 @@ function baseSecuritySeries(
|
|
|
1185
1198
|
const symbol = instrumentLabel(spec.source);
|
|
1186
1199
|
const statementCurrency = field.id.startsWith("fundamental.") && field.unit.startsWith("currency")
|
|
1187
1200
|
? reportingCurrencySeries(points, financials.financialCurrency) : null;
|
|
1188
|
-
|
|
1201
|
+
// A broker contract names its currency even before (or without) a quote.
|
|
1202
|
+
const currency = statementCurrency ? statementCurrency.currency : financials.quote?.currency || quoteMetadata?.currency || spec.source.instrument.instrument?.currency;
|
|
1189
1203
|
const assetKind = resolveAssetDisplayKind({ assetCategory: financials.quote?.instrumentType || quoteMetadata?.instrumentType });
|
|
1190
1204
|
const unknownBondBasis = hasUnknownBondHistoryBasis(financials.quote,
|
|
1191
1205
|
spec.source.instrument.instrument?.secType, quoteMetadata?.instrumentType);
|
|
@@ -1220,10 +1234,25 @@ function baseSecuritySeries(
|
|
|
1220
1234
|
|| financials.quote?.listingExchangeName
|
|
1221
1235
|
|| financials.quote?.exchangeName;
|
|
1222
1236
|
const marketField = isMarketFieldId(spec.source.fieldId);
|
|
1237
|
+
// A routing code such as a broker's SMART names no venue. The quote's
|
|
1238
|
+
// listing then supplies the session clock, so closed hours stay off the axis.
|
|
1239
|
+
const sessionExchange = [marketExchange, financials.quote?.listingExchangeName, quoteMetadata?.listingExchangeName, financials.quote?.exchangeName]
|
|
1240
|
+
.find((value) => resolveExchangeTimeZone(value));
|
|
1241
|
+
// A dollar listing saved with IBKR's SMART route and no other venue trades on
|
|
1242
|
+
// the US session, and so does a dollar option, which names no venue at all.
|
|
1243
|
+
const dollars = !currency || currency === "USD";
|
|
1244
|
+
const usOption = dollars && (spec.source.instrument.instrument?.secType?.toUpperCase() === "OPT"
|
|
1245
|
+
|| /^OPTION$/i.test(financials.quote?.instrumentType ?? quoteMetadata?.instrumentType ?? ""));
|
|
1246
|
+
const routedUsListing = !sessionExchange && ((canonicalExchange(marketExchange) === "SMART" && dollars) || usOption);
|
|
1223
1247
|
const marketTimeZone = marketField
|
|
1224
1248
|
&& canonicalExchange(marketExchange) !== "CCC"
|
|
1225
|
-
? resolveExchangeTimeZone(
|
|
1249
|
+
? resolveExchangeTimeZone(sessionExchange) ?? (routedUsListing ? "America/New_York" : null)
|
|
1226
1250
|
: null;
|
|
1251
|
+
// A route has no hours of its own: its sessions, which the day view and
|
|
1252
|
+
// VWAP follow, are the venue's, and NYSE's for an unnamed dollar listing.
|
|
1253
|
+
const listingExchange = canonicalExchange(marketExchange) === "SMART" || usOption
|
|
1254
|
+
? canonicalExchange(sessionExchange) || (routedUsListing ? "NYSE" : marketExchange)
|
|
1255
|
+
: marketExchange;
|
|
1227
1256
|
const latestChangePercent = marketField && field.unit.startsWith("currency")
|
|
1228
1257
|
&& !unknownBondBasis
|
|
1229
1258
|
&& financials.quote && hasValidQuoteObservationTime(financials.quote)
|
|
@@ -1259,12 +1288,12 @@ function baseSecuritySeries(
|
|
|
1259
1288
|
panelId: spec.panelId,
|
|
1260
1289
|
interpolation: spec.interpolation,
|
|
1261
1290
|
observationKind: marketField ? "market" : undefined,
|
|
1262
|
-
...(marketField ? { listing: { symbol: spec.source.instrument.symbol, exchange:
|
|
1291
|
+
...(marketField ? { listing: { symbol: spec.source.instrument.symbol, exchange: listingExchange ?? "" } } : {}),
|
|
1263
1292
|
timeBasis: marketTimeZone
|
|
1264
1293
|
? {
|
|
1265
1294
|
kind: "market",
|
|
1266
1295
|
timeZone: marketTimeZone,
|
|
1267
|
-
exchange: canonicalExchange(
|
|
1296
|
+
exchange: canonicalExchange(sessionExchange) || undefined,
|
|
1268
1297
|
cadenceMs: marketResolution
|
|
1269
1298
|
? CHART_RESOLUTION_STEP_MS[marketResolution]
|
|
1270
1299
|
: undefined,
|
|
@@ -1487,6 +1516,7 @@ function applyStudyPresentationTransforms(
|
|
|
1487
1516
|
visibleBounds: DateBounds,
|
|
1488
1517
|
fallbackBaselineBounds?: DateBounds,
|
|
1489
1518
|
priceComparison?: PriceComparison | null,
|
|
1519
|
+
view: DateBounds = visibleBounds,
|
|
1490
1520
|
): ResolvedSeries[] {
|
|
1491
1521
|
const rawById = new Map(rawSeries.map((series) => [series.id, series] as const));
|
|
1492
1522
|
return outputs.map((output) => {
|
|
@@ -1502,7 +1532,7 @@ function applyStudyPresentationTransforms(
|
|
|
1502
1532
|
: undefined;
|
|
1503
1533
|
const transformed = applyResolvedSeriesTransform(output, input.transform, { baseline });
|
|
1504
1534
|
const inputBounds = priceComparison?.sourceBounds?.[input.id];
|
|
1505
|
-
return inputBounds ? { ...transformed, points:
|
|
1535
|
+
return inputBounds ? { ...transformed, points: comparedBufferPoints(transformed.points, inputBounds, view) } : transformed;
|
|
1506
1536
|
});
|
|
1507
1537
|
}
|
|
1508
1538
|
|
|
@@ -1612,6 +1642,7 @@ async function resolveChartSpecPass(
|
|
|
1612
1642
|
&& entry.source.kind === "security" && isFundamentalFieldId(entry.source.fieldId))
|
|
1613
1643
|
? [FINANCIAL_VINTAGE_NOTICE] : [];
|
|
1614
1644
|
const priorityWarnings: string[] = [];
|
|
1645
|
+
const delistings = new Map<string, { symbol: string; label: string; notice: string }>();
|
|
1615
1646
|
const baseSeriesIds = new Set(spec.series.map((entry) => entry.id));
|
|
1616
1647
|
const visibleSeriesIds = new Set(spec.series
|
|
1617
1648
|
.filter((entry) => entry.visible !== false)
|
|
@@ -1813,10 +1844,10 @@ async function resolveChartSpecPass(
|
|
|
1813
1844
|
const hasExplicitWindow = explicitBounds(spec) !== null || movedWindow;
|
|
1814
1845
|
const initialCalculationBounds = calculationBounds(spec, requestVisibleBounds, initialResolution);
|
|
1815
1846
|
|
|
1816
|
-
// Only US
|
|
1847
|
+
// Only US stocks and funds trade before and after the regular session; asking elsewhere costs a request for nothing.
|
|
1817
1848
|
const extendedHoursFor = (source: Extract<ChartSeriesSpec["source"], { kind: "security" }>, resolution: ManualChartResolution) => (
|
|
1818
1849
|
spec.viewport.extendedHours === true && isMarketFieldId(source.fieldId) && isIntradayResolution(resolution)
|
|
1819
|
-
&&
|
|
1850
|
+
&& tradesUsExtendedHours(source.instrument.exchange, source.instrument.instrument)
|
|
1820
1851
|
);
|
|
1821
1852
|
// A history starts after the visible window because its source keeps no more.
|
|
1822
1853
|
let retainedHistoryOnly = false;
|
|
@@ -1952,6 +1983,14 @@ async function resolveChartSpecPass(
|
|
|
1952
1983
|
const history = loaded.points;
|
|
1953
1984
|
const coverageNotice = historyCoverageNotice(loaded.coverageStart, request.visibleBounds.start);
|
|
1954
1985
|
if (coverageNotice) priorityWarnings.push(coverageNotice);
|
|
1986
|
+
// A listing that no longer trades is named so beside its bars, which end on its last session.
|
|
1987
|
+
if (loaded.delisting) {
|
|
1988
|
+
const symbol = parsePublicTickerKey(source.instrument.symbol).symbol;
|
|
1989
|
+
const label = delistingLabel(loaded.delisting);
|
|
1990
|
+
const notice = `${symbol}: ${label}`;
|
|
1991
|
+
priorityWarnings.push(notice);
|
|
1992
|
+
if (!delistings.has(notice)) delistings.set(notice, { symbol, label, notice });
|
|
1993
|
+
}
|
|
1955
1994
|
// Read per request: one recovered window answers every visible window that shares its acquisition.
|
|
1956
1995
|
if (loaded.recovery && request.visibleBounds.start !== null && request.visibleBounds.start < loaded.recovery.start) {
|
|
1957
1996
|
priorityWarnings.push(historyRetentionNotice(loaded.recovery.retention, loaded.resolution ?? request.resolution,
|
|
@@ -2294,6 +2333,7 @@ async function resolveChartSpecPass(
|
|
|
2294
2333
|
comparisonBounds,
|
|
2295
2334
|
priceComparison ? undefined : requestVisibleBounds,
|
|
2296
2335
|
priceComparison,
|
|
2336
|
+
bounds,
|
|
2297
2337
|
),
|
|
2298
2338
|
];
|
|
2299
2339
|
warnings.push(...studyResult.warnings);
|
|
@@ -2301,7 +2341,7 @@ async function resolveChartSpecPass(
|
|
|
2301
2341
|
}
|
|
2302
2342
|
|
|
2303
2343
|
const bufferedSeries = assignAxes(adoptPriceUnits(resolved, spec.series), [...spec.series, ...spec.studies], warnings)
|
|
2304
|
-
.map((entry) =>
|
|
2344
|
+
.map((entry) => bufferPriceComparison(entry, priceComparison, bounds));
|
|
2305
2345
|
resolved = bufferedSeries.map((entry) => {
|
|
2306
2346
|
const entryBounds = presentationBounds(entry, spec.studies, priceComparison, bounds);
|
|
2307
2347
|
return entryBounds.start !== null && entryBounds.end !== null
|
|
@@ -2388,6 +2428,7 @@ async function resolveChartSpecPass(
|
|
|
2388
2428
|
loading: false,
|
|
2389
2429
|
errors,
|
|
2390
2430
|
warnings: [...new Set([...priorityWarnings, ...warnings])],
|
|
2431
|
+
...(delistings.size ? { delistings: [...delistings.values()] } : {}),
|
|
2391
2432
|
viewport,
|
|
2392
2433
|
resolution,
|
|
2393
2434
|
};
|
package/src/time-series/spec.ts
CHANGED
|
@@ -85,7 +85,7 @@ export function coerceSeriesInterpolationForStyle(
|
|
|
85
85
|
return style === "step" ? "step-after" : "none";
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
export const MAX_CHART_SERIES =
|
|
88
|
+
export const MAX_CHART_SERIES = 20;
|
|
89
89
|
|
|
90
90
|
export const DEFAULT_CHART_SPEC: ChartSpec = Object.freeze({
|
|
91
91
|
version: CHART_SPEC_VERSION,
|
package/src/time-series/types.ts
CHANGED
|
@@ -281,6 +281,11 @@ export interface ChartResolutionResult {
|
|
|
281
281
|
loading: boolean;
|
|
282
282
|
errors: string[];
|
|
283
283
|
warnings: string[];
|
|
284
|
+
/**
|
|
285
|
+
* Each charted listing that no longer trades: its symbol and label
|
|
286
|
+
* ("Delisted Oct 27, 2022"), which `warnings` carries as "TWTR: Delisted Oct 27, 2022".
|
|
287
|
+
*/
|
|
288
|
+
delistings?: { symbol: string; label: string; notice: string }[];
|
|
284
289
|
/** Effective inclusive bounds used to clip the resolved chart data. */
|
|
285
290
|
viewport?: { start: Date; end: Date };
|
|
286
291
|
/** Bar resolution the loaded market history was fetched at. */
|
package/src/types/broker.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
BrokerOrderPreview,
|
|
12
12
|
BrokerOrderRequest,
|
|
13
13
|
BrokerPortfolioPerformance,
|
|
14
|
+
BrokerTradingCapabilities,
|
|
14
15
|
} from "./trading";
|
|
15
16
|
import type { CachePolicy, CachePolicyMap } from "./persistence";
|
|
16
17
|
|
|
@@ -85,10 +86,44 @@ export interface BrokerProfileAction {
|
|
|
85
86
|
disabledReason?: string;
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
/** How a chart asks a broker for history beyond the regular session. */
|
|
90
|
+
export interface BrokerHistoryOptions {
|
|
91
|
+
/** Include pre-market and after-hours bars. Only a broker that sets `extendedHoursHistory` is asked. */
|
|
92
|
+
session?: "extended";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** What a broker's order stream calls. */
|
|
96
|
+
export interface BrokerOrderListener {
|
|
97
|
+
/**
|
|
98
|
+
* An order as the broker now reports it: placed, partly or wholly filled,
|
|
99
|
+
* replaced, cancelled, rejected or expired. The same `orderId` names the
|
|
100
|
+
* order through all of them, as `getOrderStatus` and `cancelOrder` take it.
|
|
101
|
+
*/
|
|
102
|
+
order(order: BrokerOrder): void;
|
|
103
|
+
/**
|
|
104
|
+
* Whether changes are being pushed now. While false, as before the first
|
|
105
|
+
* call, the host polls open orders instead; on true it reconciles once.
|
|
106
|
+
*/
|
|
107
|
+
live?(live: boolean): void;
|
|
108
|
+
}
|
|
109
|
+
|
|
88
110
|
export interface BrokerAdapter {
|
|
89
111
|
readonly id: string;
|
|
90
112
|
readonly name: string;
|
|
91
113
|
readonly cachePolicy?: CachePolicyMap;
|
|
114
|
+
/**
|
|
115
|
+
* Intraday history includes pre-market and after-hours bars when its
|
|
116
|
+
* `BrokerHistoryOptions` ask for the extended session. The chart then
|
|
117
|
+
* starts the day at the pre-market open; without it, the regular session.
|
|
118
|
+
*/
|
|
119
|
+
readonly extendedHoursHistory?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* The quotes and bars this broker supplies stay on the user's device: Gloom Cloud sync never
|
|
122
|
+
* uploads them, for a broker whose terms restrict redistributing its market data. Its accounts
|
|
123
|
+
* sync like any broker's, unless the user keeps a connection on the device (`localOnly` on the
|
|
124
|
+
* profile). Hosts that honor it export `LOCAL_BROKER_MARKET_DATA` from `gloomberb/broker`.
|
|
125
|
+
*/
|
|
126
|
+
readonly localMarketData?: boolean;
|
|
92
127
|
validate(instance: BrokerInstanceConfig): Promise<boolean>;
|
|
93
128
|
importPositions(instance: BrokerInstanceConfig): Promise<BrokerPosition[]>;
|
|
94
129
|
importPortfolioSnapshot?(instance: BrokerInstanceConfig): Promise<{ accounts: BrokerAccount[]; positions: BrokerPosition[] }>;
|
|
@@ -116,9 +151,10 @@ export interface BrokerAdapter {
|
|
|
116
151
|
bufferRange: TimeRange,
|
|
117
152
|
resolution: ManualChartResolution,
|
|
118
153
|
instrument?: BrokerContractRef | null,
|
|
154
|
+
options?: BrokerHistoryOptions,
|
|
119
155
|
): Promise<PricePoint[]>;
|
|
120
156
|
/** Fetch higher-resolution price data for a specific date window (e.g. when zoomed in). */
|
|
121
|
-
getDetailedPriceHistory?(ticker: string, instance: BrokerInstanceConfig, exchange: string, startDate: Date, endDate: Date, barSize: string, instrument?: BrokerContractRef | null): Promise<PricePoint[]>;
|
|
157
|
+
getDetailedPriceHistory?(ticker: string, instance: BrokerInstanceConfig, exchange: string, startDate: Date, endDate: Date, barSize: string, instrument?: BrokerContractRef | null, options?: BrokerHistoryOptions): Promise<PricePoint[]>;
|
|
122
158
|
getChartResolutionSupport?(
|
|
123
159
|
ticker: string,
|
|
124
160
|
instance: BrokerInstanceConfig,
|
|
@@ -136,6 +172,8 @@ export interface BrokerAdapter {
|
|
|
136
172
|
* Whether this profile can stream quotes at all. Return false for modes that
|
|
137
173
|
* only sync statements, so their positions stream from the cloud instead.
|
|
138
174
|
* Quotes are also only routed to a broker whose status is "connected".
|
|
175
|
+
* Answer from the profile's configuration, not from session state: the
|
|
176
|
+
* desktop app asks in its view, while the session lives in the backend.
|
|
139
177
|
*/
|
|
140
178
|
canStreamQuotes?(instance: BrokerInstanceConfig): boolean;
|
|
141
179
|
subscribeQuotes?(
|
|
@@ -144,8 +182,25 @@ export interface BrokerAdapter {
|
|
|
144
182
|
onQuote: (target: QuoteSubscriptionTarget, quote: Quote) => void,
|
|
145
183
|
): () => void;
|
|
146
184
|
listOpenOrders?(instance: BrokerInstanceConfig): Promise<BrokerOrder[]>;
|
|
185
|
+
/**
|
|
186
|
+
* Pushes order changes as the broker reports them, for every account the
|
|
187
|
+
* profile includes. The host baselines from `listOpenOrders` and polls it
|
|
188
|
+
* while `live` is false or never called; see BrokerOrderListener.
|
|
189
|
+
*/
|
|
190
|
+
subscribeOrders?(instance: BrokerInstanceConfig, listener: BrokerOrderListener): () => void;
|
|
191
|
+
/** Explicit status, including terminal orders. Null means unconfirmed, never cancelled by absence. */
|
|
192
|
+
getOrderStatus?(instance: BrokerInstanceConfig, orderId: number): Promise<BrokerOrder | null>;
|
|
147
193
|
listExecutions?(instance: BrokerInstanceConfig): Promise<BrokerExecution[]>;
|
|
194
|
+
/** Opt into the shared ticket with supported combinations and current trading permission. */
|
|
195
|
+
getTradingCapabilities?(instance: BrokerInstanceConfig, contract?: BrokerContractRef, orderType?: BrokerOrderRequest["orderType"]): BrokerTradingCapabilities;
|
|
196
|
+
/** Config patch for the host's explicit enable/disable trading confirmation. Must not perform I/O. */
|
|
197
|
+
getTradingConfigUpdate?(instance: BrokerInstanceConfig, enabled: boolean): Record<string, unknown>;
|
|
148
198
|
previewOrder?(instance: BrokerInstanceConfig, request: BrokerOrderRequest): Promise<BrokerOrderPreview>;
|
|
199
|
+
/**
|
|
200
|
+
* Place, change and cancel throw when the broker did not take the action. An error with `refused: true` says the
|
|
201
|
+
* broker answered no, or nothing was sent: the order is as it was, and its message is one short line the app shows as
|
|
202
|
+
* it is ("Aries cannot find this order"). Any other error is a lost answer, whose outcome the app treats as unknown.
|
|
203
|
+
*/
|
|
149
204
|
placeOrder?(instance: BrokerInstanceConfig, request: BrokerOrderRequest): Promise<BrokerOrder>;
|
|
150
205
|
modifyOrder?(instance: BrokerInstanceConfig, orderId: number, request: BrokerOrderRequest): Promise<BrokerOrder>;
|
|
151
206
|
cancelOrder?(instance: BrokerInstanceConfig, orderId: number): Promise<void>;
|
package/src/types/config.ts
CHANGED
|
@@ -18,6 +18,12 @@ export interface BrokerInstanceConfig {
|
|
|
18
18
|
config: Record<string, unknown>;
|
|
19
19
|
enabled?: boolean;
|
|
20
20
|
lastSyncedAt?: number;
|
|
21
|
+
/**
|
|
22
|
+
* The user keeps this connection's data on this device: Cloud sync leaves out the profile, its
|
|
23
|
+
* accounts' portfolios and account values, and the positions in them. Daily roundups, the weekly
|
|
24
|
+
* brief, position alerts, the mobile app and Ask Gloom's portfolio context then go without it.
|
|
25
|
+
*/
|
|
26
|
+
localOnly?: boolean;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
export interface ColumnConfig {
|
|
@@ -119,7 +119,11 @@ export interface MarketDataRequestContext {
|
|
|
119
119
|
brokerId?: string;
|
|
120
120
|
brokerInstanceId?: string;
|
|
121
121
|
instrument?: BrokerContractRef | null;
|
|
122
|
-
|
|
122
|
+
/**
|
|
123
|
+
* "refresh" always fetches. "current" refetches a stored value past its stale time before answering, and
|
|
124
|
+
* answers with it only if that fetch fails: for a caller that cannot see a refresh landing in the background.
|
|
125
|
+
*/
|
|
126
|
+
cacheMode?: "default" | "refresh" | "current";
|
|
123
127
|
statementHistory?: "extended";
|
|
124
128
|
/** A validated, same-target detailed retry constrained to one exhausted source. */
|
|
125
129
|
historyRecovery?: import("../sources/history-retention").HistoryRecoveryCandidate;
|
package/src/types/financials.ts
CHANGED
|
@@ -34,6 +34,20 @@ export interface QuoteProvenance {
|
|
|
34
34
|
rejectedPriceProviders?: string[];
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* A US listing that no longer trades, as Gloom Cloud states it: TWTR, last
|
|
39
|
+
* traded 2022-10-27 and removed from NYSE the next day. Its daily history is
|
|
40
|
+
* still served, ending on that session. Dates are New York trading dates.
|
|
41
|
+
*/
|
|
42
|
+
export interface ListingDelisting {
|
|
43
|
+
/** "delisted": the exchange's removal of the listing is on record; "inactive": only its last trade is known. */
|
|
44
|
+
status: "delisted" | "inactive";
|
|
45
|
+
/** The last session with a trade (YYYY-MM-DD). */
|
|
46
|
+
lastTradeDate: string;
|
|
47
|
+
/** The first session served (YYYY-MM-DD). */
|
|
48
|
+
historyStart: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
37
51
|
/** Static listing facts from a quote observation, without an investable price. */
|
|
38
52
|
export interface QuoteMetadataSource {
|
|
39
53
|
providerId?: string;
|
|
@@ -81,6 +95,13 @@ export interface Quote extends DepositaryReceiptFacts {
|
|
|
81
95
|
providerPriceDivisor?: number;
|
|
82
96
|
change: number;
|
|
83
97
|
changePercent: number;
|
|
98
|
+
/**
|
|
99
|
+
* What change and changePercent measure from when it is not previousClose,
|
|
100
|
+
* as Gloom Cloud says of a coin whose previous close it cannot date to the
|
|
101
|
+
* last two days: the last 24 hours ("24h", previousClose absent), or
|
|
102
|
+
* nothing ("none", no move). Absent on every other quote.
|
|
103
|
+
*/
|
|
104
|
+
changeBasis?: "24h" | "none";
|
|
84
105
|
previousClose?: number;
|
|
85
106
|
/** Official close of a completed regular session, in this quote's currency. */
|
|
86
107
|
regularClose?: number;
|
|
@@ -100,6 +121,8 @@ export interface Quote extends DepositaryReceiptFacts {
|
|
|
100
121
|
receivedAt?: number; // local receipt timestamp ms for streamed/display freshness
|
|
101
122
|
delivery?: QuoteDelivery;
|
|
102
123
|
stale?: boolean;
|
|
124
|
+
/** The listing no longer trades: the price is its last trade (ListingDelisting). */
|
|
125
|
+
delisting?: ListingDelisting;
|
|
103
126
|
exchangeName?: string;
|
|
104
127
|
fullExchangeName?: string;
|
|
105
128
|
listingExchangeName?: string;
|
package/src/types/headless.ts
CHANGED
|
@@ -103,6 +103,8 @@ export interface HeadlessPaneContext {
|
|
|
103
103
|
/** Effective instance settings, after template creation and option normalization. */
|
|
104
104
|
settings?: Record<string, unknown>;
|
|
105
105
|
capabilities?: CapabilityInvoker;
|
|
106
|
+
/** Locally registered broker adapters. Reports may read account data; they never submit orders. */
|
|
107
|
+
resolveBroker?: (brokerType: string) => import("./broker").BrokerAdapter | null;
|
|
106
108
|
/** Read one local portfolio without passing holdings through a remote endpoint. */
|
|
107
109
|
resolvePortfolio?: (id: string) => Promise<{
|
|
108
110
|
portfolio: import("./ticker").Portfolio;
|
|
@@ -131,6 +133,12 @@ export interface HeadlessPaneColumn {
|
|
|
131
133
|
header: string;
|
|
132
134
|
/** Print `header` as written, a field name such as `source`, where reports would otherwise title-case it. */
|
|
133
135
|
literalHeader?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* What the text report heads the column with when it can say more than an
|
|
138
|
+
* export's header should (`Chg % UTC` over `Chg %`), printed as written;
|
|
139
|
+
* CSV keeps `header`, so a column read by its name stays where it was.
|
|
140
|
+
*/
|
|
141
|
+
textHeader?: string;
|
|
134
142
|
align?: "left" | "right" | "center";
|
|
135
143
|
width?: number;
|
|
136
144
|
/** False keeps the column whole when the text table is fitted to a narrow width, so a time or an id is never cut. */
|
|
@@ -229,9 +237,11 @@ export interface HeadlessPaneFreshness {
|
|
|
229
237
|
/**
|
|
230
238
|
* How often the data is published, when its dates mark the start of a period
|
|
231
239
|
* (a monthly statistic is dated the 1st). The report then names the month,
|
|
232
|
-
* quarter or year, not a day it was never observed on.
|
|
240
|
+
* quarter or year, not a day it was never observed on. A semiannual estimate
|
|
241
|
+
* is dated by the month it was made in (USDA's coffee and sugar balances),
|
|
242
|
+
* so it names that month.
|
|
233
243
|
*/
|
|
234
|
-
periodicity?: "monthly" | "quarterly" | "annual";
|
|
244
|
+
periodicity?: "monthly" | "quarterly" | "semiannual" | "annual";
|
|
235
245
|
/**
|
|
236
246
|
* The newest row is a weekly or monthly bar whose period had not ended when
|
|
237
247
|
* it was served (October's bar on 16 Oct), and the last day it covers so
|
package/src/types/instrument.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ListingDelisting } from "./financials";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* What a ticker is, at the grain that decides which research has data for it:
|
|
3
5
|
* filings, executives and analyst coverage exist for a company's stock,
|
|
@@ -77,4 +79,6 @@ export interface InstrumentSearchResult {
|
|
|
77
79
|
* carries that name. A leftover listing of the merged company (GOGLC on
|
|
78
80
|
* BYMA) is linked to it by search, naming the `successor`. */
|
|
79
81
|
formerIssuer?: { symbol: string; name: string; until: string; successor?: { symbol: string; name: string | null } };
|
|
82
|
+
/** A US listing that no longer trades, found by its exact symbol (TWTR). Untrusted until parsed. */
|
|
83
|
+
delisting?: ListingDelisting;
|
|
80
84
|
}
|