gloomberb 0.7.2 → 0.7.4
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 +79 -132
- package/package.json +1 -1
- package/src/app.tsx +73 -67
- package/src/components/chart/chart-data.ts +7 -2
- package/src/components/chart/chart-pane-settings.ts +7 -4
- package/src/components/chart/chart-renderer.ts +158 -47
- package/src/components/chart/chart-types.ts +20 -2
- package/src/components/chart/chart.test.ts +179 -13
- package/src/components/chart/comparison-chart-renderer.test.ts +14 -30
- package/src/components/chart/comparison-chart-renderer.ts +50 -11
- package/src/components/chart/comparison-stock-chart.tsx +129 -129
- package/src/components/chart/market-session.ts +187 -0
- package/src/components/chart/native/chart-rasterizer.ts +108 -19
- package/src/components/chart/price-axis-labels.tsx +25 -9
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +97 -1
- package/src/components/chart/stock-chart.tsx +95 -49
- package/src/components/chart/time-axis-label.tsx +56 -2
- package/src/components/command-bar/command-bar.test.tsx +446 -120
- package/src/components/command-bar/command-bar.tsx +535 -384
- package/src/components/command-bar/command-registry.ts +16 -6
- package/src/components/command-bar/list-model.ts +8 -4
- package/src/components/command-bar/root-shortcuts.ts +4 -5
- package/src/components/command-bar/view-model.test.ts +21 -0
- package/src/components/command-bar/view-model.ts +17 -4
- package/src/components/command-bar/workflow-ops.ts +11 -32
- package/src/components/data-table-view.test.tsx +24 -1
- package/src/components/data-table-view.tsx +3 -2
- package/src/components/feed-data-table-stack-view.tsx +3 -2
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/floating-pane.tsx +9 -2
- package/src/components/layout/pane-footer.tsx +1 -1
- package/src/components/layout/pane-header.tsx +4 -2
- package/src/components/layout/pane-sizing.ts +12 -0
- package/src/components/layout/pane.tsx +24 -2
- package/src/components/layout/shell.tsx +24 -10
- package/src/components/layout/status-bar.test.tsx +4 -1
- package/src/components/layout/status-bar.tsx +4 -24
- package/src/components/markdown-text.tsx +53 -8
- package/src/components/onboarding/onboarding-wizard.tsx +7 -6
- package/src/components/pane-settings-dialog.tsx +9 -6
- package/src/components/pane-template-wizard.tsx +3 -2
- package/src/components/price-selector-dialog.tsx +3 -2
- package/src/components/price-sparkline-view.tsx +463 -0
- package/src/components/price-sparkline.ts +46 -0
- package/src/components/table-view-shared.tsx +21 -3
- package/src/components/ticker-list-table-view.tsx +3 -2
- package/src/components/ticker-list-table.tsx +15 -66
- package/src/components/toggle-list.tsx +3 -3
- package/src/components/ui/choice-dialog.tsx +3 -2
- package/src/components/ui/multi-select-dialog.tsx +3 -2
- package/src/components/ui/native-select.tsx +3 -3
- package/src/core/state/app-state.ts +210 -80
- package/src/core/state/session-persistence.ts +25 -0
- package/src/data/config-store-node.ts +113 -25
- package/src/data/config-store.test.ts +65 -3
- package/src/data/plugin-state-store.ts +39 -13
- package/src/data/sqlite-cache.test.ts +13 -0
- package/src/market-data/coordinator-subscriptions.test.ts +102 -18
- package/src/market-data/coordinator.test.ts +122 -2
- package/src/market-data/coordinator.ts +200 -56
- package/src/market-data/hooks.test.tsx +31 -0
- package/src/market-data/hooks.tsx +1 -0
- package/src/news/aggregator.test.ts +41 -0
- package/src/news/aggregator.ts +61 -9
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +25 -6
- package/src/plugins/builtin/ai/screener-pane.tsx +3 -0
- package/src/plugins/builtin/ai/settings.ts +3 -0
- package/src/plugins/builtin/broker-manager/index.tsx +4 -3
- package/src/plugins/builtin/chat-controller.test.ts +398 -17
- package/src/plugins/builtin/chat-controller.ts +263 -48
- package/src/plugins/builtin/chat.test.tsx +312 -6
- package/src/plugins/builtin/chat.tsx +245 -66
- package/src/plugins/builtin/comparison-chart.test.tsx +117 -3
- package/src/plugins/builtin/correlation/colors.test.ts +39 -0
- package/src/plugins/builtin/correlation/colors.ts +87 -0
- package/src/plugins/builtin/correlation/index.test.tsx +151 -0
- package/src/plugins/builtin/correlation/index.tsx +92 -21
- package/src/plugins/builtin/debug.tsx +4 -3
- package/src/plugins/builtin/econ/index.tsx +3 -2
- package/src/plugins/builtin/help.test.tsx +2 -2
- package/src/plugins/builtin/holders/index.tsx +25 -13
- package/src/plugins/builtin/layout-manager.tsx +1 -7
- package/src/plugins/builtin/market-movers/index.tsx +72 -40
- package/src/plugins/builtin/news-wire/breaking-pane.test.tsx +156 -0
- package/src/plugins/builtin/news-wire/breaking-pane.tsx +0 -90
- package/src/plugins/builtin/news-wire/index.tsx +1 -3
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +3 -2
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +4 -1
- package/src/plugins/builtin/news-wire/persisted-articles.ts +7 -1
- package/src/plugins/builtin/options.tsx +9 -17
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +6 -10
- package/src/plugins/builtin/portfolio-list/metrics.ts +13 -2
- package/src/plugins/builtin/portfolio-list/mutations.ts +17 -0
- package/src/plugins/builtin/portfolio-list/pane.tsx +139 -23
- package/src/plugins/builtin/portfolio-list/quick-add.tsx +422 -0
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -12
- package/src/plugins/builtin/portfolio-list/table.tsx +3 -3
- package/src/plugins/builtin/portfolio-list.test.tsx +311 -9
- package/src/plugins/builtin/quote-monitor.test.tsx +158 -25
- package/src/plugins/builtin/sectors/index.tsx +275 -43
- package/src/plugins/builtin/sectors/sector-data.ts +41 -1
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +30 -9
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +25 -15
- package/src/plugins/builtin/ticker-detail/pane.tsx +18 -7
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +445 -63
- package/src/plugins/builtin/ticker-detail/settings.ts +84 -3
- package/src/plugins/builtin/ticker-detail.test.tsx +2 -2
- package/src/plugins/builtin/world-indices/index.tsx +58 -19
- package/src/plugins/ibkr/trading-pane.tsx +3 -2
- package/src/plugins/plugin-runtime.tsx +2 -2
- package/src/plugins/prediction-markets/settings.ts +4 -0
- package/src/plugins/registry.test.ts +55 -0
- package/src/plugins/registry.ts +11 -4
- package/src/plugins/ticker-navigation.test.ts +35 -5
- package/src/plugins/ticker-navigation.ts +16 -22
- package/src/renderers/electrobun/bun/application-menu-click.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu-click.ts +7 -4
- package/src/renderers/electrobun/bun/application-menu.ts +6 -1
- package/src/renderers/electrobun/bun/desktop-workspace.ts +47 -29
- package/src/renderers/electrobun/bun/index.ts +52 -13
- package/src/renderers/electrobun/view/app-services.ts +51 -9
- package/src/renderers/electrobun/view/backend-rpc.ts +5 -8
- package/src/renderers/electrobun/view/desktop-controls.tsx +4 -4
- package/src/renderers/electrobun/view/main.tsx +0 -16
- package/src/renderers/electrobun/view/styles.css +23 -21
- package/src/renderers/electrobun/view/ui-host.tsx +6 -4
- package/src/renderers/opentui/host.tsx +2 -1
- package/src/renderers/opentui/start.tsx +1 -1
- package/src/renderers/opentui/test-utils.tsx +6 -5
- package/src/sources/gloomberb-cloud.test.ts +55 -2
- package/src/sources/gloomberb-cloud.ts +180 -29
- package/src/sources/provider-router.test.ts +567 -10
- package/src/sources/provider-router.ts +323 -93
- package/src/sources/yahoo-finance.test.ts +37 -0
- package/src/sources/yahoo-finance.ts +48 -2
- package/src/state/app-bootstrap.test.ts +50 -23
- package/src/state/app-bootstrap.ts +29 -13
- package/src/state/app-context.test.ts +113 -0
- package/src/state/app-context.tsx +8 -4
- package/src/state/session-persistence.test.ts +5 -3
- package/src/state/use-inline-tickers.ts +3 -0
- package/src/state/use-quote-streaming.ts +11 -0
- package/src/theme/colors.ts +13 -14
- package/src/types/config.ts +9 -4
- package/src/types/data-provider.ts +5 -1
- package/src/types/financials.ts +1 -0
- package/src/types/plugin.ts +8 -1
- package/src/ui/context-menu.tsx +4 -4
- package/src/utils/api-client.test.ts +248 -0
- package/src/utils/api-client.ts +178 -26
- package/src/utils/exchanges.test.ts +16 -0
- package/src/utils/exchanges.ts +33 -0
- package/src/utils/fuzzy-search.ts +12 -1
- package/src/utils/keyboard.ts +31 -0
- package/src/utils/options.ts +1 -1
- package/src/utils/price-history.test.ts +28 -1
- package/src/utils/price-history.ts +16 -0
- package/src/utils/quote-freshness.test.ts +150 -0
- package/src/utils/quote-freshness.ts +42 -1
- package/src/utils/quote-resolution.ts +2 -0
- package/src/utils/quote-time.test.ts +34 -0
- package/src/utils/quote-time.ts +18 -5
- package/src/utils/ticker-search.test.ts +2 -2
- package/src/utils/ticker-search.ts +48 -1
- package/src/version.ts +1 -1
- package/src/plugins/builtin/news-wire/digest-store.ts +0 -44
|
@@ -27,12 +27,13 @@ import {
|
|
|
27
27
|
type CloudMarketResponse,
|
|
28
28
|
type CloudNewsPayload,
|
|
29
29
|
type CloudNewsStoryItemPayload,
|
|
30
|
+
type CloudOptionsChainPayload,
|
|
30
31
|
type CloudPricePointPayload,
|
|
31
32
|
type CloudQuotePayload,
|
|
32
33
|
} from "../utils/api-client";
|
|
33
34
|
import type { NewsArticle, NewsQuery, NewsStoryItem } from "../types/news-source";
|
|
34
35
|
import { normalizePriceValueByDivisor, resolveCurrencyUnit } from "../utils/currency-units";
|
|
35
|
-
import { canonicalTickerKey, publicExchange } from "../utils/exchanges";
|
|
36
|
+
import { canonicalTickerKey, publicExchange, resolveExchangeTimeZone } from "../utils/exchanges";
|
|
36
37
|
import { collectNewsDisplayTickers } from "../news/ticker-symbols";
|
|
37
38
|
import { createProviderMiss } from "./provider-errors";
|
|
38
39
|
|
|
@@ -42,7 +43,6 @@ const CLOUD_RESOLUTION_SUPPORT = normalizeChartResolutionSupport([
|
|
|
42
43
|
{ resolution: "5m", maxRange: "1M" },
|
|
43
44
|
{ resolution: "15m", maxRange: "3M" },
|
|
44
45
|
{ resolution: "30m", maxRange: "6M" },
|
|
45
|
-
{ resolution: "45m", maxRange: "6M" },
|
|
46
46
|
{ resolution: "1h", maxRange: "1Y" },
|
|
47
47
|
{ resolution: "1d", maxRange: "ALL" },
|
|
48
48
|
{ resolution: "1wk", maxRange: "ALL" },
|
|
@@ -54,6 +54,7 @@ const CLOUD_PROVIDER_MISS_PATTERNS = [
|
|
|
54
54
|
/figi.*missing or invalid/i,
|
|
55
55
|
/error in the query/i,
|
|
56
56
|
];
|
|
57
|
+
type CloudProviderMeta = NonNullable<CloudMarketResponse<unknown>["providerMeta"]>;
|
|
57
58
|
|
|
58
59
|
function isCloudProviderMiss(error: unknown): boolean {
|
|
59
60
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -71,10 +72,20 @@ async function withCloudFallback<T>(load: () => Promise<T>, message: string): Pr
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
function
|
|
75
|
+
function cloudInternalProviderId(providerMeta?: CloudProviderMeta): string | null {
|
|
76
|
+
const upstream = providerMeta?.provider ?? providerMeta?.upstream;
|
|
77
|
+
return upstream ? `${providerId}:${upstream}` : null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isStaleCloudResponse(response: CloudMarketResponse<unknown>): boolean {
|
|
81
|
+
return response.stale === true || response.providerMeta?.stale === true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function mapQuote(quote: CloudQuotePayload, providerMeta?: CloudProviderMeta): Quote {
|
|
75
85
|
const { currency, divisor } = resolveCurrencyUnit(quote.currency);
|
|
76
86
|
const listingExchangeName = quote.listingExchangeName ?? quote.exchangeName;
|
|
77
87
|
const listingExchangeFullName = quote.listingExchangeFullName ?? quote.fullExchangeName ?? listingExchangeName;
|
|
88
|
+
const internalProviderId = cloudInternalProviderId(providerMeta);
|
|
78
89
|
return {
|
|
79
90
|
...quote,
|
|
80
91
|
currency: currency || quote.currency,
|
|
@@ -98,12 +109,100 @@ function mapQuote(quote: CloudQuotePayload): Quote {
|
|
|
98
109
|
exchangeName: listingExchangeName,
|
|
99
110
|
fullExchangeName: listingExchangeFullName,
|
|
100
111
|
providerId,
|
|
112
|
+
provenance: internalProviderId
|
|
113
|
+
? {
|
|
114
|
+
...quote.provenance,
|
|
115
|
+
price: {
|
|
116
|
+
providerId: internalProviderId,
|
|
117
|
+
dataSource: quote.dataSource,
|
|
118
|
+
},
|
|
119
|
+
session: {
|
|
120
|
+
providerId: internalProviderId,
|
|
121
|
+
dataSource: quote.dataSource,
|
|
122
|
+
},
|
|
123
|
+
routing: {
|
|
124
|
+
providerId,
|
|
125
|
+
dataSource: quote.dataSource,
|
|
126
|
+
},
|
|
127
|
+
fields: {
|
|
128
|
+
...quote.provenance?.fields,
|
|
129
|
+
cloudProvider: { providerId: internalProviderId, dataSource: quote.dataSource },
|
|
130
|
+
...(providerMeta?.fallbackReason
|
|
131
|
+
? { fallbackReason: { providerId: providerMeta.fallbackReason } }
|
|
132
|
+
: {}),
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
: quote.provenance,
|
|
101
136
|
};
|
|
102
137
|
}
|
|
103
138
|
|
|
104
|
-
|
|
139
|
+
const LOCAL_DATE_TIME_PATTERN = /^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,3})?)?)?$/;
|
|
140
|
+
const EXPLICIT_TIME_ZONE_PATTERN = /(?:Z|[+-]\d{2}:?\d{2})$/i;
|
|
141
|
+
|
|
142
|
+
function getZonedDateParts(date: Date, timeZone: string): Map<string, string> {
|
|
143
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
144
|
+
timeZone,
|
|
145
|
+
hourCycle: "h23",
|
|
146
|
+
year: "numeric",
|
|
147
|
+
month: "2-digit",
|
|
148
|
+
day: "2-digit",
|
|
149
|
+
hour: "2-digit",
|
|
150
|
+
minute: "2-digit",
|
|
151
|
+
second: "2-digit",
|
|
152
|
+
});
|
|
153
|
+
const parts = new Map<string, string>();
|
|
154
|
+
for (const part of formatter.formatToParts(date)) {
|
|
155
|
+
if (part.type !== "literal") parts.set(part.type, part.value);
|
|
156
|
+
}
|
|
157
|
+
return parts;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function getTimeZoneOffsetMs(utcMs: number, timeZone: string): number {
|
|
161
|
+
const parts = getZonedDateParts(new Date(utcMs), timeZone);
|
|
162
|
+
const zonedAsUtcMs = Date.UTC(
|
|
163
|
+
Number(parts.get("year")),
|
|
164
|
+
Number(parts.get("month")) - 1,
|
|
165
|
+
Number(parts.get("day")),
|
|
166
|
+
Number(parts.get("hour")),
|
|
167
|
+
Number(parts.get("minute")),
|
|
168
|
+
Number(parts.get("second")),
|
|
169
|
+
);
|
|
170
|
+
return zonedAsUtcMs - utcMs;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function exchangeLocalDateTimeToUtc(match: RegExpMatchArray, timeZone: string): Date {
|
|
174
|
+
const year = Number(match[1]);
|
|
175
|
+
const month = Number(match[2]);
|
|
176
|
+
const day = Number(match[3]);
|
|
177
|
+
const hour = Number(match[4] ?? "0");
|
|
178
|
+
const minute = Number(match[5] ?? "0");
|
|
179
|
+
const second = Number(match[6] ?? "0");
|
|
180
|
+
const localAsUtcMs = Date.UTC(year, month - 1, day, hour, minute, second);
|
|
181
|
+
const firstOffset = getTimeZoneOffsetMs(localAsUtcMs, timeZone);
|
|
182
|
+
const firstUtcMs = localAsUtcMs - firstOffset;
|
|
183
|
+
const verifiedOffset = getTimeZoneOffsetMs(firstUtcMs, timeZone);
|
|
184
|
+
return new Date(localAsUtcMs - verifiedOffset);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function parseCloudPricePointDate(value: Date | string | number, exchange: string): Date {
|
|
188
|
+
if (value instanceof Date || typeof value === "number") return new Date(value);
|
|
189
|
+
if (EXPLICIT_TIME_ZONE_PATTERN.test(value)) return new Date(value);
|
|
190
|
+
|
|
191
|
+
const match = value.match(LOCAL_DATE_TIME_PATTERN);
|
|
192
|
+
if (!match) return new Date(value);
|
|
193
|
+
|
|
194
|
+
const hasTime = match[4] !== undefined;
|
|
195
|
+
if (!hasTime) {
|
|
196
|
+
return new Date(`${match[1]}-${match[2]}-${match[3]}T00:00:00Z`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const timeZone = resolveExchangeTimeZone(exchange);
|
|
200
|
+
return timeZone ? exchangeLocalDateTimeToUtc(match, timeZone) : new Date(value);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function mapPricePoint(point: CloudPricePointPayload, divisor = 1, exchange = ""): PricePoint {
|
|
105
204
|
return {
|
|
106
|
-
date:
|
|
205
|
+
date: parseCloudPricePointDate(point.date, exchange),
|
|
107
206
|
open: normalizePriceValueByDivisor(point.open, divisor),
|
|
108
207
|
high: normalizePriceValueByDivisor(point.high, divisor),
|
|
109
208
|
low: normalizePriceValueByDivisor(point.low, divisor),
|
|
@@ -112,8 +211,8 @@ function mapPricePoint(point: CloudPricePointPayload, divisor = 1): PricePoint {
|
|
|
112
211
|
};
|
|
113
212
|
}
|
|
114
213
|
|
|
115
|
-
function mapCloudFinancials(financials: TickerFinancials): TickerFinancials {
|
|
116
|
-
const quote = financials.quote ? mapQuote(financials.quote as CloudQuotePayload) : undefined;
|
|
214
|
+
function mapCloudFinancials(financials: TickerFinancials, providerMeta?: CloudProviderMeta): TickerFinancials {
|
|
215
|
+
const quote = financials.quote ? mapQuote(financials.quote as CloudQuotePayload, providerMeta) : undefined;
|
|
117
216
|
const divisor = quote ? resolveCurrencyUnit(quote.currency).divisor : 1;
|
|
118
217
|
return {
|
|
119
218
|
quote,
|
|
@@ -130,6 +229,15 @@ function mapCloudFinancials(financials: TickerFinancials): TickerFinancials {
|
|
|
130
229
|
};
|
|
131
230
|
}
|
|
132
231
|
|
|
232
|
+
function mapOptionsChain(chain: CloudOptionsChainPayload): OptionsChain {
|
|
233
|
+
return {
|
|
234
|
+
underlyingSymbol: chain.underlyingSymbol,
|
|
235
|
+
expirationDates: chain.expirationDates ?? [],
|
|
236
|
+
calls: chain.calls ?? [],
|
|
237
|
+
puts: chain.puts ?? [],
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
133
241
|
function mapBatchError<T>(
|
|
134
242
|
item: CloudMarketBatchItem<T>,
|
|
135
243
|
fallbackMessage: string,
|
|
@@ -273,10 +381,18 @@ function padTimePart(value: number): string {
|
|
|
273
381
|
return String(value).padStart(2, "0");
|
|
274
382
|
}
|
|
275
383
|
|
|
276
|
-
function formatCloudDateTime(date: Date, includeTime: boolean): string {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
384
|
+
function formatCloudDateTime(date: Date, includeTime: boolean, exchange = ""): string {
|
|
385
|
+
if (includeTime) {
|
|
386
|
+
const timeZone = resolveExchangeTimeZone(exchange);
|
|
387
|
+
if (timeZone) {
|
|
388
|
+
const parts = getZonedDateParts(date, timeZone);
|
|
389
|
+
return `${parts.get("year")}-${parts.get("month")}-${parts.get("day")} ${parts.get("hour")}:${parts.get("minute")}:${parts.get("second")}`;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const year = includeTime ? date.getFullYear() : date.getUTCFullYear();
|
|
394
|
+
const month = padTimePart((includeTime ? date.getMonth() : date.getUTCMonth()) + 1);
|
|
395
|
+
const day = padTimePart(includeTime ? date.getDate() : date.getUTCDate());
|
|
280
396
|
if (!includeTime) {
|
|
281
397
|
return `${year}-${month}-${day}`;
|
|
282
398
|
}
|
|
@@ -396,7 +512,13 @@ export class GloomberbCloudProvider implements AssetDataProvider {
|
|
|
396
512
|
await requireVerifiedSession();
|
|
397
513
|
return withCloudFallback(async () => {
|
|
398
514
|
const response = await apiClient.getCloudFinancials(ticker, exchange);
|
|
399
|
-
|
|
515
|
+
if (isStaleCloudResponse(response)) {
|
|
516
|
+
throw createProviderMiss(`Cloud financials are stale for ${ticker}`);
|
|
517
|
+
}
|
|
518
|
+
return mapCloudFinancials(
|
|
519
|
+
unwrapRequiredCloudResponse(response, `Cloud financials are unavailable for ${ticker}`),
|
|
520
|
+
response.providerMeta,
|
|
521
|
+
);
|
|
400
522
|
}, `Cloud financials are unavailable for ${ticker}`);
|
|
401
523
|
}
|
|
402
524
|
|
|
@@ -437,10 +559,16 @@ export class GloomberbCloudProvider implements AssetDataProvider {
|
|
|
437
559
|
async getQuote(ticker: string, exchange = "", _context?: MarketDataRequestContext): Promise<Quote> {
|
|
438
560
|
await requireVerifiedSession();
|
|
439
561
|
return withCloudFallback(
|
|
440
|
-
async () =>
|
|
441
|
-
await apiClient.getCloudQuote(ticker, exchange)
|
|
442
|
-
|
|
443
|
-
|
|
562
|
+
async () => {
|
|
563
|
+
const response = await apiClient.getCloudQuote(ticker, exchange);
|
|
564
|
+
if (isStaleCloudResponse(response)) {
|
|
565
|
+
throw createProviderMiss(`Cloud quotes are stale for ${ticker}`);
|
|
566
|
+
}
|
|
567
|
+
return mapQuote(
|
|
568
|
+
unwrapRequiredCloudResponse(response, `Cloud quotes are unavailable for ${ticker}`),
|
|
569
|
+
response.providerMeta,
|
|
570
|
+
);
|
|
571
|
+
},
|
|
444
572
|
`Cloud quotes are unavailable for ${ticker}`,
|
|
445
573
|
);
|
|
446
574
|
}
|
|
@@ -550,9 +678,12 @@ export class GloomberbCloudProvider implements AssetDataProvider {
|
|
|
550
678
|
() => apiClient.getCloudHistory(ticker, exchange, request),
|
|
551
679
|
`Cloud chart data is unavailable for ${ticker}`,
|
|
552
680
|
);
|
|
553
|
-
|
|
681
|
+
if (isStaleCloudResponse(response)) {
|
|
682
|
+
throw createProviderMiss(`Cloud chart data is stale for ${ticker}`);
|
|
683
|
+
}
|
|
684
|
+
const { divisor } = resolveCurrencyUnit(response.currency ?? response.providerMeta?.currency);
|
|
554
685
|
return unwrapRequiredCloudResponse(response, `Cloud chart data is unavailable for ${ticker}`)
|
|
555
|
-
.map((point) => mapPricePoint(point, divisor));
|
|
686
|
+
.map((point) => mapPricePoint(point, divisor, exchange));
|
|
556
687
|
}
|
|
557
688
|
|
|
558
689
|
async getPriceHistoryForResolution(
|
|
@@ -570,14 +701,17 @@ export class GloomberbCloudProvider implements AssetDataProvider {
|
|
|
570
701
|
const response = await withCloudFallback(
|
|
571
702
|
() => apiClient.getCloudHistory(ticker, exchange, {
|
|
572
703
|
interval,
|
|
573
|
-
startDate: formatCloudDateTime(startDate, includeTime),
|
|
574
|
-
endDate: formatCloudDateTime(endDate, includeTime),
|
|
704
|
+
startDate: formatCloudDateTime(startDate, includeTime, exchange),
|
|
705
|
+
endDate: formatCloudDateTime(endDate, includeTime, exchange),
|
|
575
706
|
}),
|
|
576
707
|
`Cloud chart data is unavailable for ${ticker}`,
|
|
577
708
|
);
|
|
578
|
-
|
|
709
|
+
if (isStaleCloudResponse(response)) {
|
|
710
|
+
throw createProviderMiss(`Cloud chart data is stale for ${ticker}`);
|
|
711
|
+
}
|
|
712
|
+
const { divisor } = resolveCurrencyUnit(response.currency ?? response.providerMeta?.currency);
|
|
579
713
|
return unwrapRequiredCloudResponse(response, `Cloud chart data is unavailable for ${ticker}`)
|
|
580
|
-
.map((point) => mapPricePoint(point, divisor));
|
|
714
|
+
.map((point) => mapPricePoint(point, divisor, exchange));
|
|
581
715
|
}
|
|
582
716
|
|
|
583
717
|
async getDetailedPriceHistory(
|
|
@@ -594,25 +728,38 @@ export class GloomberbCloudProvider implements AssetDataProvider {
|
|
|
594
728
|
const response = await withCloudFallback(
|
|
595
729
|
() => apiClient.getCloudHistory(ticker, exchange, {
|
|
596
730
|
interval,
|
|
597
|
-
startDate: formatCloudDateTime(startDate, includeTime),
|
|
598
|
-
endDate: formatCloudDateTime(endDate, includeTime),
|
|
731
|
+
startDate: formatCloudDateTime(startDate, includeTime, exchange),
|
|
732
|
+
endDate: formatCloudDateTime(endDate, includeTime, exchange),
|
|
599
733
|
}),
|
|
600
734
|
`Cloud detailed chart history is unavailable for ${ticker}`,
|
|
601
735
|
);
|
|
602
|
-
|
|
736
|
+
if (isStaleCloudResponse(response)) {
|
|
737
|
+
throw createProviderMiss(`Cloud detailed chart history is stale for ${ticker}`);
|
|
738
|
+
}
|
|
739
|
+
const { divisor } = resolveCurrencyUnit(response.currency ?? response.providerMeta?.currency);
|
|
603
740
|
return unwrapRequiredCloudResponse(response, `Cloud detailed chart history is unavailable for ${ticker}`)
|
|
604
|
-
.map((point) => mapPricePoint(point, divisor));
|
|
741
|
+
.map((point) => mapPricePoint(point, divisor, exchange));
|
|
605
742
|
}
|
|
606
743
|
|
|
607
|
-
async getOptionsChain(
|
|
608
|
-
|
|
744
|
+
async getOptionsChain(ticker: string, exchange?: string, expirationDate?: number, _context?: MarketDataRequestContext): Promise<OptionsChain> {
|
|
745
|
+
await requireVerifiedSession();
|
|
746
|
+
return withCloudFallback(async () => {
|
|
747
|
+
const response = await apiClient.getCloudOptionsChain(ticker, exchange, expirationDate);
|
|
748
|
+
const chain = unwrapRequiredCloudResponse(
|
|
749
|
+
response,
|
|
750
|
+
`Cloud options chains are unavailable for ${ticker}`,
|
|
751
|
+
);
|
|
752
|
+
return mapOptionsChain(chain);
|
|
753
|
+
}, `Cloud options chains are unavailable for ${ticker}`);
|
|
609
754
|
}
|
|
610
755
|
|
|
611
756
|
subscribeQuotes(
|
|
612
757
|
targets: QuoteSubscriptionTarget[],
|
|
613
758
|
onQuote: (target: QuoteSubscriptionTarget, quote: Quote) => void,
|
|
614
759
|
): () => void {
|
|
615
|
-
|
|
760
|
+
if (apiClient.getSessionToken()) {
|
|
761
|
+
void apiClient.ensureVerifiedSession().catch(() => {});
|
|
762
|
+
}
|
|
616
763
|
const targetMap = new Map<string, QuoteSubscriptionTarget[]>();
|
|
617
764
|
for (const target of targets) {
|
|
618
765
|
const key = quoteTargetKey(target.symbol, target.exchange);
|
|
@@ -625,6 +772,10 @@ export class GloomberbCloudProvider implements AssetDataProvider {
|
|
|
625
772
|
targets.map((target) => ({
|
|
626
773
|
symbol: target.symbol,
|
|
627
774
|
exchange: target.exchange,
|
|
775
|
+
surface: target.surface,
|
|
776
|
+
visible: target.visible,
|
|
777
|
+
selected: target.selected,
|
|
778
|
+
weight: target.weight,
|
|
628
779
|
})),
|
|
629
780
|
(target, quote) => {
|
|
630
781
|
const key = quoteTargetKey(target.symbol, target.exchange);
|