gloomberb 0.12.0 → 0.13.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 +2 -0
- package/package.json +1 -2
- package/src/api-client/auth.ts +2 -1
- package/src/api-client/index.test.ts +34 -0
- package/src/api-client/index.ts +10 -2
- package/src/api-client/request.ts +4 -1
- package/src/api-client/research-activity.ts +61 -0
- package/src/api-client/types.ts +2 -1
- package/src/cli/commands/plugins.ts +4 -3
- package/src/cli/desktop-pane-shot.ts +79 -10
- package/src/cli/pane-functions/capabilities.ts +76 -23
- package/src/cli/pane-functions/cloud-session.test.ts +54 -0
- package/src/cli/pane-functions/cloud-session.ts +38 -0
- package/src/cli/pane-functions/dom.test.ts +41 -0
- package/src/cli/pane-functions/dom.ts +199 -0
- package/src/cli/pane-functions/index.test.ts +13 -2
- package/src/cli/pane-functions/index.ts +9 -5
- package/src/cli/pane-functions/intraday-shot.test.ts +307 -0
- package/src/cli/pane-functions/intraday-shot.ts +334 -0
- package/src/cli/pane-functions/report.test.ts +29 -1
- package/src/cli/pane-functions/report.ts +68 -7
- package/src/cli/pane-functions/screenshot.ts +195 -42
- package/src/cli/restore-plugins.ts +10 -3
- package/src/components/chart/composite/composite-chart.test.tsx +68 -1
- package/src/components/chart/composite/composite-chart.tsx +87 -21
- package/src/components/chart/composite/rasterizer.ts +33 -0
- package/src/components/chart/composite/renderers.test.ts +51 -4
- package/src/components/chart/composite/scene.test.ts +128 -2
- package/src/components/chart/composite/scene.ts +92 -16
- package/src/components/chart/composite/text-renderer.ts +13 -0
- package/src/components/chart/composite/time-scale.ts +30 -10
- package/src/components/chart/composite/types.ts +26 -0
- package/src/components/command-bar/assist/runtime.ts +38 -30
- package/src/components/command-bar/surface/assist.test.tsx +63 -9
- package/src/components/command-bar/surface/test-harness.tsx +23 -4
- package/src/components/onboarding/account-step/chooser-panel.tsx +2 -2
- package/src/components/onboarding/account-step/qr-panel.tsx +2 -10
- package/src/components/onboarding/onboarding-wizard.test.tsx +38 -12
- package/src/components/onboarding/onboarding-wizard.tsx +49 -14
- package/src/data/config/store/index.test.ts +23 -0
- package/src/data/config/store/node.ts +7 -1
- package/src/data/config/store/normalize.ts +4 -0
- package/src/news/types.ts +1 -0
- package/src/plugins/builtin/cds/client.test.ts +20 -0
- package/src/plugins/builtin/cds/client.ts +13 -3
- package/src/plugins/builtin/cds/headless.test.ts +30 -0
- package/src/plugins/builtin/cds/headless.ts +113 -0
- package/src/plugins/builtin/cds/index.tsx +4 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -1
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chat/controller/channels.ts +2 -1
- package/src/plugins/builtin/chat/controller/index.ts +13 -5
- package/src/plugins/builtin/chat/controller/realtime.ts +24 -0
- package/src/plugins/builtin/chat/controller/session-runtime.ts +76 -8
- package/src/plugins/builtin/chat/controller/state.ts +1 -0
- package/src/plugins/builtin/chat/controller.test.ts +260 -4
- package/src/plugins/builtin/cloud/askg/executor.test.ts +160 -0
- package/src/plugins/builtin/cloud/askg/executor.ts +429 -0
- package/src/plugins/builtin/cloud/askg/manifest.test.ts +153 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +314 -0
- package/src/plugins/builtin/cloud/askg/protocol.test.ts +190 -0
- package/src/plugins/builtin/cloud/askg/protocol.ts +286 -0
- package/src/plugins/builtin/cloud/askg/undo.test.ts +44 -0
- package/src/plugins/builtin/cloud/askg/undo.ts +277 -0
- package/src/plugins/builtin/cloud/askg/verify.ts +130 -0
- package/src/plugins/builtin/cloud/auth-dialog.tsx +7 -1
- package/src/plugins/builtin/cloud/device-signin-dialog.tsx +21 -8
- package/src/plugins/builtin/cloud/plugin.tsx +2 -0
- package/src/plugins/builtin/cloud/verification-panel.tsx +56 -0
- package/src/plugins/builtin/congress-trades/client.ts +11 -0
- package/src/plugins/builtin/congress-trades/detail.tsx +3 -3
- package/src/plugins/builtin/congress-trades/headless.test.ts +98 -0
- package/src/plugins/builtin/congress-trades/headless.ts +145 -0
- package/src/plugins/builtin/congress-trades/pane.tsx +3 -3
- package/src/plugins/builtin/credit-conditions/client.ts +1 -1
- package/src/plugins/builtin/credit-conditions/headless.test.ts +31 -0
- package/src/plugins/builtin/credit-conditions/headless.ts +55 -0
- package/src/plugins/builtin/credit-conditions/index.tsx +4 -0
- package/src/plugins/builtin/dividend-yield/headless.test.ts +88 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +135 -0
- package/src/plugins/builtin/dividend-yield/index.tsx +12 -8
- package/src/plugins/builtin/dividend-yield/model.ts +4 -22
- package/src/plugins/builtin/dividend-yield/view.ts +17 -0
- package/src/plugins/builtin/earnings/headless.test.ts +82 -0
- package/src/plugins/builtin/earnings/headless.ts +162 -0
- package/src/plugins/builtin/earnings/index.tsx +2 -0
- package/src/plugins/builtin/earnings-calls/data.ts +29 -8
- package/src/plugins/builtin/earnings-calls/headless.test.ts +192 -0
- package/src/plugins/builtin/earnings-calls/headless.ts +300 -0
- package/src/plugins/builtin/earnings-calls/index.tsx +19 -9
- package/src/plugins/builtin/earnings-calls/model.ts +238 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +4 -0
- package/src/plugins/builtin/earnings-calls/transcript-view.tsx +13 -11
- package/src/plugins/builtin/fear-greed/headless.test.ts +53 -0
- package/src/plugins/builtin/fear-greed/headless.ts +84 -0
- package/src/plugins/builtin/fear-greed/index.tsx +4 -0
- package/src/plugins/builtin/holders/client.ts +29 -0
- package/src/plugins/builtin/holders/headless.test.ts +58 -0
- package/src/plugins/builtin/holders/headless.ts +162 -0
- package/src/plugins/builtin/holders/index.tsx +14 -8
- package/src/plugins/builtin/holders/pane.tsx +7 -1
- package/src/plugins/builtin/insider/client.ts +50 -0
- package/src/plugins/builtin/insider/headless.test.ts +122 -0
- package/src/plugins/builtin/insider/headless.ts +117 -0
- package/src/plugins/builtin/insider/index.tsx +21 -50
- package/src/plugins/builtin/insider/insider-data.ts +1 -1
- package/src/plugins/builtin/insider/model.ts +72 -0
- package/src/plugins/builtin/ipo-calendar/client.ts +15 -0
- package/src/plugins/builtin/ipo-calendar/headless.test.ts +89 -0
- package/src/plugins/builtin/ipo-calendar/headless.ts +127 -0
- package/src/plugins/builtin/ipo-calendar/index.tsx +2 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +3 -15
- package/src/plugins/builtin/market-movers/client.ts +76 -0
- package/src/plugins/builtin/market-movers/headless.test.ts +34 -0
- package/src/plugins/builtin/market-movers/headless.ts +73 -0
- package/src/plugins/builtin/market-movers/index.tsx +11 -51
- package/src/plugins/builtin/news/headless.test.ts +138 -0
- package/src/plugins/builtin/news/headless.ts +190 -0
- package/src/plugins/builtin/news/index.tsx +13 -9
- package/src/plugins/builtin/news/wire/index.tsx +2 -1
- package/src/plugins/builtin/research/analyst-headless.test.ts +60 -0
- package/src/plugins/builtin/research/analyst-headless.ts +172 -0
- package/src/plugins/builtin/research/analyst-model.ts +219 -0
- package/src/plugins/builtin/research/analyst-pane.tsx +31 -221
- package/src/plugins/builtin/research/client.ts +12 -0
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -237
- package/src/plugins/builtin/research/event-model.ts +245 -0
- package/src/plugins/builtin/research/events-headless.test.ts +32 -0
- package/src/plugins/builtin/research/events-headless.ts +87 -0
- package/src/plugins/builtin/research/headless.test.ts +93 -0
- package/src/plugins/builtin/research/headless.ts +161 -0
- package/src/plugins/builtin/research/index.tsx +32 -18
- package/src/plugins/builtin/research-search/pane.tsx +4 -0
- package/src/plugins/builtin/scanner/headless.test.ts +59 -0
- package/src/plugins/builtin/scanner/headless.ts +125 -0
- package/src/plugins/builtin/scanner/index.ts +4 -0
- package/src/plugins/builtin/sec/client.ts +16 -0
- package/src/plugins/builtin/sec/headless.test.ts +72 -0
- package/src/plugins/builtin/sec/headless.ts +69 -0
- package/src/plugins/builtin/sec/index.tsx +20 -75
- package/src/plugins/builtin/sec/model.ts +89 -0
- package/src/plugins/builtin/sectors/client.ts +54 -0
- package/src/plugins/builtin/sectors/headless.test.ts +28 -0
- package/src/plugins/builtin/sectors/headless.ts +80 -0
- package/src/plugins/builtin/sectors/index.tsx +6 -51
- package/src/plugins/builtin/shared/cloud-upgrade.ts +8 -4
- package/src/plugins/builtin/short-interest/client.ts +5 -2
- package/src/plugins/builtin/short-interest/headless.test.ts +96 -0
- package/src/plugins/builtin/short-interest/headless.ts +111 -0
- package/src/plugins/builtin/short-interest/index.tsx +15 -9
- package/src/plugins/builtin/thirteenf/data.ts +13 -11
- package/src/plugins/builtin/thirteenf/headless.test.ts +112 -0
- package/src/plugins/builtin/thirteenf/headless.ts +286 -0
- package/src/plugins/builtin/thirteenf/index.tsx +4 -0
- package/src/plugins/builtin/thirteenf/model.ts +13 -0
- package/src/plugins/builtin/thirteenf/table.ts +3 -15
- package/src/plugins/builtin/ticker-detail/pane.tsx +14 -0
- package/src/plugins/builtin/treasury-auctions/headless.test.ts +34 -0
- package/src/plugins/builtin/treasury-auctions/headless.ts +95 -0
- package/src/plugins/builtin/treasury-auctions/index.tsx +4 -0
- package/src/plugins/builtin/volatility/client.ts +16 -3
- package/src/plugins/builtin/volatility/headless.test.ts +35 -0
- package/src/plugins/builtin/volatility/headless.ts +75 -0
- package/src/plugins/builtin/volatility/index.tsx +4 -0
- package/src/plugins/builtin/world-indices/client.ts +43 -0
- package/src/plugins/builtin/world-indices/headless.test.ts +32 -0
- package/src/plugins/builtin/world-indices/headless.ts +109 -0
- package/src/plugins/builtin/world-indices/index.tsx +4 -0
- package/src/plugins/builtin/yield-curve/headless.test.ts +24 -0
- package/src/plugins/builtin/yield-curve/headless.ts +67 -0
- package/src/plugins/builtin/yield-curve/index.tsx +6 -8
- package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -6
- package/src/plugins/bundle.test.ts +58 -1
- package/src/plugins/bundle.ts +4 -2
- package/src/plugins/catalog-backend.test.ts +26 -0
- package/src/plugins/catalog-backend.ts +9 -2
- package/src/plugins/catalog-browser.test.ts +0 -13
- package/src/plugins/catalog-browser.ts +4 -0
- package/src/plugins/catalog-manifest.test.ts +15 -0
- package/src/plugins/catalog-manifest.ts +12 -5
- package/src/plugins/catalog-ui.ts +0 -6
- package/src/plugins/host-contract.ts +9 -0
- package/src/plugins/host-modules.ts +12 -0
- package/src/plugins/loader.ts +63 -15
- package/src/plugins/prediction-markets/headless.test.ts +119 -0
- package/src/plugins/prediction-markets/headless.ts +255 -0
- package/src/plugins/prediction-markets/index.tsx +2 -0
- package/src/plugins/seed.ts +3 -0
- package/src/public/broker.ts +6 -0
- package/src/remote/schema.test.ts +65 -0
- package/src/remote/schema.ts +322 -39
- package/src/remote/types.ts +49 -2
- package/src/renderers/browser/app-services.ts +3 -0
- package/src/renderers/browser/config-host.ts +24 -8
- package/src/renderers/browser/deeplink-bridge.test.ts +11 -0
- package/src/renderers/browser/deeplink-bridge.ts +15 -3
- package/src/renderers/browser/main.tsx +3 -1
- package/src/renderers/browser/research-entry.test.ts +8 -0
- package/src/renderers/browser/research-entry.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +2 -2
- package/src/renderers/electrobun/bun/desktop/initialization.ts +10 -3
- package/src/renderers/electrobun/bun/external-plugins.ts +20 -4
- package/src/renderers/electrobun/view/build-assets.ts +0 -2
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +63 -10
- package/src/sources/gloomberb-cloud/news.ts +1 -0
- package/src/sync/core-contributors.test.ts +111 -0
- package/src/sync/core-contributors.ts +155 -1
- package/src/time-series/resolve.test.ts +118 -1
- package/src/time-series/resolve.ts +14 -16
- package/src/types/config.ts +2 -0
- package/src/plugins/broker-sync/broker-sync.test.ts +0 -188
- package/src/plugins/broker-sync/native-loader.ts +0 -10
- package/src/plugins/broker-sync/normalize.ts +0 -308
- package/src/plugins/broker-sync/public.ts +0 -122
- package/src/plugins/broker-sync/robinhood-native.ts +0 -367
- package/src/plugins/broker-sync/robinhood.ts +0 -114
- package/src/plugins/broker-sync/simplefin-native.ts +0 -201
- package/src/plugins/broker-sync/simplefin.ts +0 -70
- package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +0 -7
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import type { DataProvider } from "../../types/data-provider";
|
|
2
|
+
import type { PricePoint } from "../../types/financials";
|
|
3
|
+
import type { TimeRange } from "../../time-series/range";
|
|
4
|
+
import {
|
|
5
|
+
getPresetResolution,
|
|
6
|
+
isIntradayResolution,
|
|
7
|
+
type ManualChartResolution,
|
|
8
|
+
} from "../../time-series/resolution";
|
|
9
|
+
import { resolveExchangeTimeZone } from "../../utils/exchanges";
|
|
10
|
+
import { getPricePointTimestamp } from "../../utils/price-history";
|
|
11
|
+
import { zonedWallClockToUtcMs } from "../../utils/zoned-date-time";
|
|
12
|
+
|
|
13
|
+
export type ShotIntradayRangePreset = "1D" | "1W";
|
|
14
|
+
|
|
15
|
+
export interface ShotIntradayRequest {
|
|
16
|
+
rangePreset: ShotIntradayRangePreset;
|
|
17
|
+
resolution: ManualChartResolution;
|
|
18
|
+
session: string | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ShotIntradayWindow {
|
|
22
|
+
points: PricePoint[];
|
|
23
|
+
sessionDates: string[];
|
|
24
|
+
start: Date | null;
|
|
25
|
+
end: Date | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
29
|
+
const HISTORICAL_RETRY_DELAY_MS = 61 * 60_000;
|
|
30
|
+
const DAY_MS = 24 * 60 * 60_000;
|
|
31
|
+
|
|
32
|
+
function sessionDateFormatter(timeZone: string): Intl.DateTimeFormat {
|
|
33
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
34
|
+
timeZone,
|
|
35
|
+
year: "numeric",
|
|
36
|
+
month: "2-digit",
|
|
37
|
+
day: "2-digit",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function sessionDate(timestamp: number, timeZone: string): string {
|
|
42
|
+
const parts = new Map(
|
|
43
|
+
sessionDateFormatter(timeZone)
|
|
44
|
+
.formatToParts(new Date(timestamp))
|
|
45
|
+
.filter((part) => part.type !== "literal")
|
|
46
|
+
.map((part) => [part.type, part.value]),
|
|
47
|
+
);
|
|
48
|
+
return `${parts.get("year")}-${parts.get("month")}-${parts.get("day")}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function parseShotSessionDate(value: string): {
|
|
52
|
+
year: number;
|
|
53
|
+
month: number;
|
|
54
|
+
day: number;
|
|
55
|
+
} {
|
|
56
|
+
const match = DATE_ONLY_PATTERN.exec(value.trim());
|
|
57
|
+
if (!match) throw new Error(`Invalid --session value "${value}". Use YYYY-MM-DD.`);
|
|
58
|
+
const year = Number(match[1]);
|
|
59
|
+
const month = Number(match[2]);
|
|
60
|
+
const day = Number(match[3]);
|
|
61
|
+
const candidate = new Date(Date.UTC(year, month - 1, day));
|
|
62
|
+
if (
|
|
63
|
+
candidate.getUTCFullYear() !== year
|
|
64
|
+
|| candidate.getUTCMonth() !== month - 1
|
|
65
|
+
|| candidate.getUTCDate() !== day
|
|
66
|
+
) {
|
|
67
|
+
throw new Error(`Invalid --session value "${value}". Use a real calendar date.`);
|
|
68
|
+
}
|
|
69
|
+
return { year, month, day };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function shotSessionUtcBounds(value: string, timeZone: string): {
|
|
73
|
+
start: Date;
|
|
74
|
+
end: Date;
|
|
75
|
+
} {
|
|
76
|
+
const parsed = parseShotSessionDate(value);
|
|
77
|
+
const next = new Date(Date.UTC(parsed.year, parsed.month - 1, parsed.day + 1));
|
|
78
|
+
return {
|
|
79
|
+
start: new Date(zonedWallClockToUtcMs(
|
|
80
|
+
timeZone,
|
|
81
|
+
parsed.year,
|
|
82
|
+
parsed.month,
|
|
83
|
+
parsed.day,
|
|
84
|
+
0,
|
|
85
|
+
0,
|
|
86
|
+
0,
|
|
87
|
+
)),
|
|
88
|
+
end: new Date(zonedWallClockToUtcMs(
|
|
89
|
+
timeZone,
|
|
90
|
+
next.getUTCFullYear(),
|
|
91
|
+
next.getUTCMonth() + 1,
|
|
92
|
+
next.getUTCDate(),
|
|
93
|
+
0,
|
|
94
|
+
0,
|
|
95
|
+
0,
|
|
96
|
+
)),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function normalizedPoints(points: readonly PricePoint[]): PricePoint[] {
|
|
101
|
+
const byTimestamp = new Map<number, PricePoint>();
|
|
102
|
+
for (const point of points) {
|
|
103
|
+
const timestamp = getPricePointTimestamp(point);
|
|
104
|
+
if (!Number.isFinite(timestamp) || !Number.isFinite(point.close) || point.close <= 0) continue;
|
|
105
|
+
byTimestamp.set(timestamp, {
|
|
106
|
+
...point,
|
|
107
|
+
date: point.date instanceof Date ? point.date : new Date(timestamp),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return [...byTimestamp.values()].sort(
|
|
111
|
+
(left, right) => getPricePointTimestamp(left) - getPricePointTimestamp(right),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function resolveShotIntradaySessionWindow(
|
|
116
|
+
points: readonly PricePoint[],
|
|
117
|
+
options: {
|
|
118
|
+
rangePreset: ShotIntradayRangePreset;
|
|
119
|
+
session?: string | null;
|
|
120
|
+
timeZone?: string | null;
|
|
121
|
+
},
|
|
122
|
+
): ShotIntradayWindow {
|
|
123
|
+
const timeZone = options.timeZone || "UTC";
|
|
124
|
+
const normalized = normalizedPoints(points);
|
|
125
|
+
const bySession = new Map<string, PricePoint[]>();
|
|
126
|
+
for (const point of normalized) {
|
|
127
|
+
const date = sessionDate(getPricePointTimestamp(point), timeZone);
|
|
128
|
+
const sessionPoints = bySession.get(date) ?? [];
|
|
129
|
+
sessionPoints.push(point);
|
|
130
|
+
bySession.set(date, sessionPoints);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const requestedSession = options.session?.trim() || null;
|
|
134
|
+
if (requestedSession) parseShotSessionDate(requestedSession);
|
|
135
|
+
const availableSessions = [...bySession.keys()].sort();
|
|
136
|
+
const selectedSessions = requestedSession
|
|
137
|
+
? availableSessions.filter((date) => date === requestedSession)
|
|
138
|
+
: options.rangePreset === "1D"
|
|
139
|
+
? availableSessions.slice(-1)
|
|
140
|
+
: availableSessions.slice(-5);
|
|
141
|
+
const selected = selectedSessions.flatMap((date) => bySession.get(date) ?? []);
|
|
142
|
+
const startTime = selected.length > 0 ? getPricePointTimestamp(selected[0]!) : Number.NaN;
|
|
143
|
+
const endTime = selected.length > 0 ? getPricePointTimestamp(selected.at(-1)!) : Number.NaN;
|
|
144
|
+
return {
|
|
145
|
+
points: selected,
|
|
146
|
+
sessionDates: selectedSessions,
|
|
147
|
+
start: Number.isFinite(startTime) ? new Date(startTime) : null,
|
|
148
|
+
end: Number.isFinite(endTime) ? new Date(endTime) : null,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function hasIntradayBars(window: ShotIntradayWindow, timeZone = "UTC"): boolean {
|
|
153
|
+
if (window.points.length < 2) return false;
|
|
154
|
+
const pointsBySession = new Map<string, number[]>();
|
|
155
|
+
for (const point of window.points) {
|
|
156
|
+
const timestamp = getPricePointTimestamp(point);
|
|
157
|
+
const date = sessionDate(timestamp, timeZone);
|
|
158
|
+
const timestamps = pointsBySession.get(date) ?? [];
|
|
159
|
+
timestamps.push(timestamp);
|
|
160
|
+
pointsBySession.set(date, timestamps);
|
|
161
|
+
}
|
|
162
|
+
return [...pointsBySession.values()].some((timestamps) => (
|
|
163
|
+
timestamps.length >= 2
|
|
164
|
+
&& timestamps.some((timestamp, index) => index > 0 && timestamp - timestamps[index - 1]! < DAY_MS)
|
|
165
|
+
));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function resolveShotIntradayRequest(options: {
|
|
169
|
+
rangePreset?: unknown;
|
|
170
|
+
chartResolution?: unknown;
|
|
171
|
+
session?: unknown;
|
|
172
|
+
}): ShotIntradayRequest {
|
|
173
|
+
const session = typeof options.session === "string" && options.session.trim()
|
|
174
|
+
? options.session.trim()
|
|
175
|
+
: null;
|
|
176
|
+
if (session) parseShotSessionDate(session);
|
|
177
|
+
const rangePreset = session
|
|
178
|
+
? "1D"
|
|
179
|
+
: options.rangePreset === "1W" ? "1W" : "1D";
|
|
180
|
+
const requestedResolution = typeof options.chartResolution === "string"
|
|
181
|
+
? options.chartResolution
|
|
182
|
+
: "auto";
|
|
183
|
+
const resolution = requestedResolution === "auto"
|
|
184
|
+
? getPresetResolution(rangePreset)
|
|
185
|
+
: requestedResolution as ManualChartResolution;
|
|
186
|
+
if (!isIntradayResolution(resolution)) {
|
|
187
|
+
throw new Error(`GIP requires an intraday chart resolution, got "${requestedResolution}".`);
|
|
188
|
+
}
|
|
189
|
+
return { rangePreset, resolution, session };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function unavailableReason(symbol: string, request: ShotIntradayRequest, kind: "empty" | "not-intraday"): string {
|
|
193
|
+
const window = request.session
|
|
194
|
+
? `session ${request.session}`
|
|
195
|
+
: request.rangePreset === "1W" ? "the latest five sessions" : "the latest session";
|
|
196
|
+
return kind === "not-intraday"
|
|
197
|
+
? `The market-data provider did not return intraday bars for ${symbol} for ${window}.`
|
|
198
|
+
: `No intraday price history is available for ${symbol} for ${window}.`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function loadTrailingHistory(
|
|
202
|
+
provider: DataProvider,
|
|
203
|
+
symbol: string,
|
|
204
|
+
exchange: string,
|
|
205
|
+
request: ShotIntradayRequest,
|
|
206
|
+
): Promise<PricePoint[]> {
|
|
207
|
+
if (!provider.getPriceHistoryForResolution) return [];
|
|
208
|
+
const fetchRange: TimeRange = request.rangePreset === "1W" && request.resolution !== "1m"
|
|
209
|
+
? "1M"
|
|
210
|
+
: "1W";
|
|
211
|
+
try {
|
|
212
|
+
return await provider.getPriceHistoryForResolution(
|
|
213
|
+
symbol,
|
|
214
|
+
exchange,
|
|
215
|
+
fetchRange,
|
|
216
|
+
request.resolution,
|
|
217
|
+
);
|
|
218
|
+
} catch {
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function loadHistoricalFallback(
|
|
224
|
+
provider: DataProvider,
|
|
225
|
+
symbol: string,
|
|
226
|
+
exchange: string,
|
|
227
|
+
request: ShotIntradayRequest,
|
|
228
|
+
now: Date,
|
|
229
|
+
): Promise<PricePoint[]> {
|
|
230
|
+
if (!provider.getDetailedPriceHistory) return [];
|
|
231
|
+
if (request.session) {
|
|
232
|
+
const timeZone = resolveExchangeTimeZone(exchange) ?? "UTC";
|
|
233
|
+
const bounds = shotSessionUtcBounds(request.session, timeZone);
|
|
234
|
+
return provider.getDetailedPriceHistory(
|
|
235
|
+
symbol,
|
|
236
|
+
exchange,
|
|
237
|
+
bounds.start,
|
|
238
|
+
bounds.end,
|
|
239
|
+
request.resolution,
|
|
240
|
+
).catch(() => []);
|
|
241
|
+
}
|
|
242
|
+
const end = new Date(now.getTime() - HISTORICAL_RETRY_DELAY_MS);
|
|
243
|
+
const lookbackDays = request.rangePreset === "1W" ? 35 : 8;
|
|
244
|
+
const start = new Date(end.getTime() - lookbackDays * DAY_MS);
|
|
245
|
+
return provider.getDetailedPriceHistory(
|
|
246
|
+
symbol,
|
|
247
|
+
exchange,
|
|
248
|
+
start,
|
|
249
|
+
end,
|
|
250
|
+
request.resolution,
|
|
251
|
+
).catch(() => []);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export async function loadShotIntradayWindow(options: {
|
|
255
|
+
provider: DataProvider;
|
|
256
|
+
symbol: string;
|
|
257
|
+
exchange: string;
|
|
258
|
+
request: ShotIntradayRequest;
|
|
259
|
+
now?: Date;
|
|
260
|
+
}): Promise<ShotIntradayWindow & { unavailableReason: string | null }> {
|
|
261
|
+
const timeZone = resolveExchangeTimeZone(options.exchange) ?? "UTC";
|
|
262
|
+
let raw = options.request.session
|
|
263
|
+
? await loadHistoricalFallback(
|
|
264
|
+
options.provider,
|
|
265
|
+
options.symbol,
|
|
266
|
+
options.exchange,
|
|
267
|
+
options.request,
|
|
268
|
+
options.now ?? new Date(),
|
|
269
|
+
)
|
|
270
|
+
: await loadTrailingHistory(
|
|
271
|
+
options.provider,
|
|
272
|
+
options.symbol,
|
|
273
|
+
options.exchange,
|
|
274
|
+
options.request,
|
|
275
|
+
);
|
|
276
|
+
let window = resolveShotIntradaySessionWindow(raw, {
|
|
277
|
+
rangePreset: options.request.rangePreset,
|
|
278
|
+
session: options.request.session,
|
|
279
|
+
timeZone,
|
|
280
|
+
});
|
|
281
|
+
let sawNonIntradayData = window.points.length > 0 && !hasIntradayBars(window, timeZone);
|
|
282
|
+
|
|
283
|
+
if (!hasIntradayBars(window, timeZone)) {
|
|
284
|
+
raw = options.request.session
|
|
285
|
+
? await loadTrailingHistory(
|
|
286
|
+
options.provider,
|
|
287
|
+
options.symbol,
|
|
288
|
+
options.exchange,
|
|
289
|
+
options.request,
|
|
290
|
+
)
|
|
291
|
+
: await loadHistoricalFallback(
|
|
292
|
+
options.provider,
|
|
293
|
+
options.symbol,
|
|
294
|
+
options.exchange,
|
|
295
|
+
options.request,
|
|
296
|
+
options.now ?? new Date(),
|
|
297
|
+
);
|
|
298
|
+
window = resolveShotIntradaySessionWindow(raw, {
|
|
299
|
+
rangePreset: options.request.rangePreset,
|
|
300
|
+
session: options.request.session,
|
|
301
|
+
timeZone,
|
|
302
|
+
});
|
|
303
|
+
sawNonIntradayData ||= window.points.length > 0 && !hasIntradayBars(window, timeZone);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (window.points.length === 0) {
|
|
307
|
+
const requestedWindow = options.request.session
|
|
308
|
+
? shotSessionUtcBounds(options.request.session, timeZone)
|
|
309
|
+
: null;
|
|
310
|
+
return {
|
|
311
|
+
...window,
|
|
312
|
+
start: requestedWindow?.start ?? window.start,
|
|
313
|
+
end: requestedWindow?.end ?? window.end,
|
|
314
|
+
unavailableReason: unavailableReason(
|
|
315
|
+
options.symbol,
|
|
316
|
+
options.request,
|
|
317
|
+
sawNonIntradayData ? "not-intraday" : "empty",
|
|
318
|
+
),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
if (!hasIntradayBars(window, timeZone)) {
|
|
322
|
+
const requestedWindow = options.request.session
|
|
323
|
+
? shotSessionUtcBounds(options.request.session, timeZone)
|
|
324
|
+
: null;
|
|
325
|
+
return {
|
|
326
|
+
points: [],
|
|
327
|
+
sessionDates: window.sessionDates,
|
|
328
|
+
start: requestedWindow?.start ?? null,
|
|
329
|
+
end: requestedWindow?.end ?? null,
|
|
330
|
+
unavailableReason: unavailableReason(options.symbol, options.request, "not-intraday"),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return { ...window, unavailableReason: null };
|
|
334
|
+
}
|
|
@@ -3,7 +3,10 @@ import { createDefaultConfig } from "../../types/config";
|
|
|
3
3
|
import { CHART_SPEC_VERSION, type ChartSpec } from "../../time-series/types";
|
|
4
4
|
import { createTestDataProvider } from "../../test-support/data-provider";
|
|
5
5
|
import { setSharedRegistryForTests } from "../../plugins/registry";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
buildFunctionReport,
|
|
8
|
+
resolvePaneFunctionReportSource,
|
|
9
|
+
} from "./report";
|
|
7
10
|
import type { ResolvedPaneFunction } from "./resolver";
|
|
8
11
|
import type { MarketContext } from "../types";
|
|
9
12
|
|
|
@@ -25,6 +28,31 @@ const spec: ChartSpec = {
|
|
|
25
28
|
|
|
26
29
|
afterEach(() => setSharedRegistryForTests(undefined));
|
|
27
30
|
|
|
31
|
+
test("selects headless before legacy reports and rendered DOM fallback", () => {
|
|
32
|
+
const base = {
|
|
33
|
+
pane: { id: "market-data", name: "Market Data" },
|
|
34
|
+
template: undefined,
|
|
35
|
+
instance: {},
|
|
36
|
+
capability: { id: "market-data", reportReadiness: "live-dom" },
|
|
37
|
+
} as unknown as ResolvedPaneFunction;
|
|
38
|
+
|
|
39
|
+
expect(resolvePaneFunctionReportSource(base)).toBe("dom");
|
|
40
|
+
expect(resolvePaneFunctionReportSource({
|
|
41
|
+
...base,
|
|
42
|
+
capability: { ...base.capability, id: "quote-comparison" },
|
|
43
|
+
})).toBe("report");
|
|
44
|
+
expect(resolvePaneFunctionReportSource({
|
|
45
|
+
...base,
|
|
46
|
+
capability: { ...base.capability, id: "quote-comparison" },
|
|
47
|
+
headless: {
|
|
48
|
+
shape: "rows",
|
|
49
|
+
argument: { kind: "none" },
|
|
50
|
+
options: [],
|
|
51
|
+
load: () => ({ rows: [] }),
|
|
52
|
+
},
|
|
53
|
+
})).toBe("headless");
|
|
54
|
+
});
|
|
55
|
+
|
|
28
56
|
test("chart composer reports accept capability-backed series", async () => {
|
|
29
57
|
const date = new Date(Date.now() - 24 * 60 * 60 * 1_000);
|
|
30
58
|
setSharedRegistryForTests({
|
|
@@ -45,9 +45,19 @@ import {
|
|
|
45
45
|
} from "./data";
|
|
46
46
|
import type { ResolvedPaneFunction } from "./resolver";
|
|
47
47
|
import { buildHeadlessFunctionReport } from "./headless";
|
|
48
|
+
import {
|
|
49
|
+
appendDomReportFooter,
|
|
50
|
+
buildDomFunctionReport,
|
|
51
|
+
} from "./dom";
|
|
52
|
+
|
|
53
|
+
export type PaneFunctionReportSource = "headless" | "report" | "dom";
|
|
48
54
|
|
|
49
55
|
export interface PaneFunctionReportData {
|
|
50
56
|
kind: string;
|
|
57
|
+
/** Added by buildFunctionReport after the selected loader completes. */
|
|
58
|
+
source?: PaneFunctionReportSource;
|
|
59
|
+
/** Added by buildFunctionReport for every successful report. */
|
|
60
|
+
elapsedMs?: number;
|
|
51
61
|
target: string;
|
|
52
62
|
capabilityId: string;
|
|
53
63
|
symbols: string[];
|
|
@@ -746,14 +756,40 @@ async function buildRelationshipReport(
|
|
|
746
756
|
};
|
|
747
757
|
}
|
|
748
758
|
|
|
749
|
-
|
|
759
|
+
const LEGACY_REPORT_CAPABILITY_IDS = new Set([
|
|
760
|
+
"chart-composer",
|
|
761
|
+
"financial-statements",
|
|
762
|
+
"fundamental-series",
|
|
763
|
+
"historical-prices",
|
|
764
|
+
"intraday-price-chart",
|
|
765
|
+
"price-chart",
|
|
766
|
+
"price-comparison",
|
|
767
|
+
"quote-comparison",
|
|
768
|
+
"relative-valuation",
|
|
769
|
+
"return-correlation",
|
|
770
|
+
"security-relationship",
|
|
771
|
+
"valuation-series",
|
|
772
|
+
]);
|
|
773
|
+
|
|
774
|
+
export function resolvePaneFunctionReportSource(
|
|
775
|
+
resolved: Pick<ResolvedPaneFunction, "headless" | "capability" | "pane" | "template" | "instance">,
|
|
776
|
+
): PaneFunctionReportSource | null {
|
|
777
|
+
if (resolved.headless) return "headless";
|
|
778
|
+
if (
|
|
779
|
+
isFinancialAnalysisFunction(resolved as ResolvedPaneFunction)
|
|
780
|
+
|| LEGACY_REPORT_CAPABILITY_IDS.has(resolved.capability.id)
|
|
781
|
+
) {
|
|
782
|
+
return "report";
|
|
783
|
+
}
|
|
784
|
+
if (resolved.capability.reportReadiness === "live-dom") return "dom";
|
|
785
|
+
return null;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
async function buildLegacyFunctionReport(
|
|
750
789
|
resolved: ResolvedPaneFunction,
|
|
751
790
|
context: MarketContext,
|
|
752
791
|
rawArg: string,
|
|
753
792
|
): Promise<PaneFunctionReport> {
|
|
754
|
-
if (resolved.headless) {
|
|
755
|
-
return buildHeadlessFunctionReport(resolved, context, rawArg);
|
|
756
|
-
}
|
|
757
793
|
if (isFinancialAnalysisFunction(resolved)) {
|
|
758
794
|
return buildFinancialStatementReport(resolved, context, rawArg);
|
|
759
795
|
}
|
|
@@ -785,12 +821,37 @@ export async function buildFunctionReport(
|
|
|
785
821
|
return buildCorrelationReport(resolved, context);
|
|
786
822
|
case "security-relationship":
|
|
787
823
|
return buildRelationshipReport(resolved, context);
|
|
824
|
+
default:
|
|
825
|
+
throw new Error(`No structured report builder is registered for ${resolved.token}.`);
|
|
788
826
|
}
|
|
827
|
+
}
|
|
789
828
|
|
|
790
|
-
|
|
791
|
-
|
|
829
|
+
export async function buildFunctionReport(
|
|
830
|
+
resolved: ResolvedPaneFunction,
|
|
831
|
+
context: MarketContext,
|
|
832
|
+
rawArg: string,
|
|
833
|
+
): Promise<PaneFunctionReport> {
|
|
834
|
+
const startedAt = performance.now();
|
|
835
|
+
const source = resolvePaneFunctionReportSource(resolved);
|
|
836
|
+
if (!source) {
|
|
837
|
+
throw new Error(`${resolved.token} is an interactive pane and does not expose a data report.`);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
const report = source === "headless"
|
|
841
|
+
? await buildHeadlessFunctionReport(resolved, context, rawArg)
|
|
842
|
+
: source === "report"
|
|
843
|
+
? await buildLegacyFunctionReport(resolved, context, rawArg)
|
|
844
|
+
: await buildDomFunctionReport(resolved, context, rawArg);
|
|
845
|
+
const elapsedMs = Math.max(0, Math.round(performance.now() - startedAt));
|
|
846
|
+
report.data = { ...report.data, source, elapsedMs };
|
|
847
|
+
if (source === "dom") {
|
|
848
|
+
report.text = appendDomReportFooter(
|
|
849
|
+
report.text,
|
|
850
|
+
elapsedMs,
|
|
851
|
+
report.data.truncated === true,
|
|
852
|
+
);
|
|
792
853
|
}
|
|
793
|
-
|
|
854
|
+
return report;
|
|
794
855
|
}
|
|
795
856
|
|
|
796
857
|
async function buildLegacyPaneReport(
|