gloomberb 0.3.7 → 0.4.0

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.
Files changed (133) hide show
  1. package/README.md +19 -8
  2. package/package.json +1 -1
  3. package/src/app.tsx +29 -65
  4. package/src/cli/base-converter.ts +31 -0
  5. package/src/cli/commands/search.ts +11 -3
  6. package/src/cli/commands/ticker.ts +22 -11
  7. package/src/cli/context.ts +27 -32
  8. package/src/cli/helpers.ts +1 -4
  9. package/src/cli/index.ts +140 -123
  10. package/src/cli/registry.test.ts +226 -0
  11. package/src/cli/registry.ts +201 -0
  12. package/src/cli.test.ts +219 -0
  13. package/src/components/chart/chart-controller.ts +309 -0
  14. package/src/components/chart/chart-data.ts +5 -6
  15. package/src/components/chart/chart-pane-settings.ts +27 -0
  16. package/src/components/chart/chart-resolution.test.ts +56 -0
  17. package/src/components/chart/chart-resolution.ts +362 -0
  18. package/src/components/chart/chart-types.ts +12 -4
  19. package/src/components/chart/chart-viewport.test.ts +25 -0
  20. package/src/components/chart/chart-viewport.ts +28 -0
  21. package/src/components/chart/comparison-chart-data.ts +20 -27
  22. package/src/components/chart/comparison-stock-chart.tsx +374 -99
  23. package/src/components/chart/stock-chart-renderer-switch.test.tsx +124 -0
  24. package/src/components/chart/stock-chart.tsx +455 -106
  25. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
  26. package/src/components/command-bar/command-bar.test.tsx +650 -5
  27. package/src/components/command-bar/command-bar.tsx +359 -63
  28. package/src/components/command-bar/command-registry.ts +8 -0
  29. package/src/components/command-bar/helpers.test.ts +23 -1
  30. package/src/components/command-bar/helpers.ts +14 -5
  31. package/src/components/command-bar/pane-template-display.ts +12 -0
  32. package/src/components/command-bar/root-shortcuts.ts +2 -1
  33. package/src/components/command-bar/workflow-ops.ts +4 -2
  34. package/src/components/detail-feed-view.test.tsx +124 -49
  35. package/src/components/detail-feed-view.tsx +396 -160
  36. package/src/components/layout/floating-pane.tsx +1 -1
  37. package/src/components/layout/pane-header.tsx +26 -2
  38. package/src/components/layout/shell.tsx +62 -50
  39. package/src/components/layout/status-bar.test.tsx +66 -0
  40. package/src/components/layout/status-bar.tsx +26 -5
  41. package/src/components/onboarding/onboarding-wizard.test.tsx +65 -0
  42. package/src/components/onboarding/onboarding-wizard.tsx +5 -6
  43. package/src/components/ticker-list-table.test.tsx +83 -0
  44. package/src/components/ticker-list-table.tsx +174 -97
  45. package/src/components/ui/data-table.tsx +27 -2
  46. package/src/components/ui/index.ts +1 -0
  47. package/src/components/ui/page-stack-view.tsx +60 -0
  48. package/src/components/use-double-click-activation.ts +31 -0
  49. package/src/index.tsx +10 -10
  50. package/src/market-data/coordinator.test.ts +92 -4
  51. package/src/market-data/coordinator.ts +32 -5
  52. package/src/market-data/hooks.test.tsx +120 -0
  53. package/src/market-data/hooks.tsx +53 -16
  54. package/src/market-data/request-types.ts +4 -2
  55. package/src/market-data/selectors.ts +24 -58
  56. package/src/plugins/builtin/chat-controller.test.ts +370 -3
  57. package/src/plugins/builtin/chat-controller.ts +310 -35
  58. package/src/plugins/builtin/chat.test.tsx +118 -6
  59. package/src/plugins/builtin/chat.tsx +175 -116
  60. package/src/plugins/builtin/comparison-chart.test.tsx +66 -5
  61. package/src/plugins/builtin/comparison-chart.tsx +21 -1
  62. package/src/plugins/builtin/news.tsx +1 -11
  63. package/src/plugins/builtin/portfolio-list/cli/commands.ts +656 -0
  64. package/src/plugins/builtin/portfolio-list/command-bar.ts +122 -0
  65. package/src/plugins/builtin/portfolio-list/header.tsx +16 -34
  66. package/src/plugins/builtin/portfolio-list/index.tsx +5 -0
  67. package/src/plugins/builtin/portfolio-list/metrics.test.ts +61 -0
  68. package/src/plugins/builtin/portfolio-list/metrics.ts +2 -2
  69. package/src/plugins/builtin/portfolio-list/mutations.test.ts +187 -0
  70. package/src/plugins/builtin/portfolio-list/mutations.ts +226 -0
  71. package/src/plugins/builtin/portfolio-list/pane.tsx +39 -17
  72. package/src/plugins/builtin/portfolio-list/table.tsx +11 -2
  73. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +163 -0
  74. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +81 -0
  75. package/src/plugins/builtin/sec.tsx +1 -11
  76. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +13 -2
  77. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +21 -0
  78. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +45 -10
  79. package/src/plugins/builtin/ticker-detail/pane.tsx +35 -17
  80. package/src/plugins/builtin/ticker-detail/settings.ts +19 -1
  81. package/src/plugins/builtin/ticker-detail.test.tsx +47 -0
  82. package/src/plugins/catalog.ts +65 -0
  83. package/src/plugins/ibkr/broker-adapter.ts +28 -0
  84. package/src/plugins/ibkr/gateway-service.test.ts +45 -1
  85. package/src/plugins/ibkr/gateway-service.ts +141 -7
  86. package/src/plugins/pane-manager.test.ts +1 -1
  87. package/src/plugins/plugin-runtime.tsx +27 -7
  88. package/src/plugins/prediction-markets/cli.ts +28 -0
  89. package/src/plugins/prediction-markets/controller-data.ts +11 -3
  90. package/src/plugins/prediction-markets/controller.ts +141 -121
  91. package/src/plugins/prediction-markets/index.tsx +2 -0
  92. package/src/plugins/prediction-markets/launch.ts +51 -16
  93. package/src/plugins/prediction-markets/metrics.ts +9 -5
  94. package/src/plugins/prediction-markets/pane.test.tsx +119 -57
  95. package/src/plugins/prediction-markets/pane.tsx +74 -153
  96. package/src/plugins/prediction-markets/table.tsx +6 -3
  97. package/src/plugins/prediction-markets/types.ts +0 -1
  98. package/src/sources/gloomberb-cloud.test.ts +29 -0
  99. package/src/sources/gloomberb-cloud.ts +89 -1
  100. package/src/sources/provider-router.test.ts +265 -3
  101. package/src/sources/provider-router.ts +239 -11
  102. package/src/sources/yahoo-finance.test.ts +26 -0
  103. package/src/sources/yahoo-finance.ts +54 -3
  104. package/src/state/app-context-selector.test.tsx +99 -0
  105. package/src/state/app-context.test.ts +18 -0
  106. package/src/state/app-context.tsx +170 -65
  107. package/src/state/selectors.ts +25 -0
  108. package/src/theme/colors.ts +8 -8
  109. package/src/theme/themes.test.ts +2 -0
  110. package/src/theme/themes.ts +22 -10
  111. package/src/types/broker.ts +21 -0
  112. package/src/types/config.ts +17 -1
  113. package/src/types/data-provider.ts +18 -0
  114. package/src/types/financials.ts +31 -1
  115. package/src/types/plugin.ts +70 -0
  116. package/src/utils/api-client.test.ts +57 -0
  117. package/src/utils/api-client.ts +35 -12
  118. package/src/utils/back-navigation.ts +27 -0
  119. package/src/utils/format.test.ts +14 -0
  120. package/src/utils/format.ts +3 -3
  121. package/src/utils/market-status.ts +46 -1
  122. package/src/utils/quote-freshness.ts +127 -0
  123. package/src/utils/quote-resolution.test.ts +277 -0
  124. package/src/utils/quote-resolution.ts +641 -0
  125. package/src/utils/slugify.ts +3 -0
  126. package/src/utils/timestamp.ts +25 -0
  127. package/src/version.ts +1 -1
  128. package/src/cli/commands/portfolio.ts +0 -210
  129. package/src/cli/commands/watchlist.ts +0 -210
  130. package/src/plugins/builtin/data-providers.ts +0 -16
  131. package/src/sources/exchange-rates.ts +0 -18
  132. package/src/utils/ascii-chart.ts +0 -172
  133. package/src/utils/ticker-tokenizer.test.ts +0 -48
@@ -0,0 +1,277 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { QuoteContributionMap } from "../types/financials";
3
+ import {
4
+ resolveCanonicalQuote,
5
+ resolveTickerFinancialsQuoteState,
6
+ upsertQuoteContributionMap,
7
+ } from "./quote-resolution";
8
+
9
+ describe("quote-resolution", () => {
10
+ test("resolves price, session, listing venue, and route from separate providers", () => {
11
+ const now = Date.parse("2026-04-08T11:00:00Z");
12
+ const contributions: QuoteContributionMap = {
13
+ ibkr: {
14
+ symbol: "AMD",
15
+ providerId: "ibkr",
16
+ dataSource: "live",
17
+ price: 100,
18
+ currency: "USD",
19
+ change: 1,
20
+ changePercent: 1,
21
+ lastUpdated: Date.parse("2026-04-08T10:59:00Z"),
22
+ listingExchangeName: "NASDAQ",
23
+ routingExchangeName: "SMART",
24
+ routingExchangeFullName: "SMART",
25
+ sessionConfidence: "unknown",
26
+ },
27
+ "gloomberb-cloud": {
28
+ symbol: "AMD",
29
+ providerId: "gloomberb-cloud",
30
+ dataSource: "delayed",
31
+ price: 99.8,
32
+ currency: "USD",
33
+ change: 0.8,
34
+ changePercent: 0.81,
35
+ lastUpdated: Date.parse("2026-04-08T10:58:00Z"),
36
+ listingExchangeName: "NASDAQ",
37
+ listingExchangeFullName: "NASDAQ",
38
+ sessionConfidence: "unknown",
39
+ },
40
+ yahoo: {
41
+ symbol: "AMD",
42
+ providerId: "yahoo",
43
+ dataSource: "yahoo",
44
+ price: 99.7,
45
+ currency: "USD",
46
+ change: 0.7,
47
+ changePercent: 0.7,
48
+ lastUpdated: Date.parse("2026-04-08T10:57:00Z"),
49
+ listingExchangeName: "NMS",
50
+ listingExchangeFullName: "NASDAQ",
51
+ marketState: "PRE",
52
+ sessionConfidence: "derived",
53
+ preMarketPrice: 101,
54
+ preMarketChange: 2,
55
+ preMarketChangePercent: 2.02,
56
+ },
57
+ };
58
+
59
+ const quote = resolveCanonicalQuote(contributions, now).quote;
60
+
61
+ expect(quote?.price).toBe(100);
62
+ expect(quote?.providerId).toBe("ibkr");
63
+ expect(quote?.marketState).toBe("PRE");
64
+ expect(quote?.preMarketPrice).toBe(101);
65
+ expect(quote?.listingExchangeName).toBe("NASDAQ");
66
+ expect(quote?.routingExchangeName).toBe("SMART");
67
+ expect(quote?.provenance?.price?.providerId).toBe("ibkr");
68
+ expect(quote?.provenance?.session?.providerId).toBe("yahoo");
69
+ });
70
+
71
+ test("prefers cloud session data over yahoo when confidence is tied", () => {
72
+ const now = Date.parse("2026-04-08T11:00:00Z");
73
+ const contributions: QuoteContributionMap = {
74
+ "gloomberb-cloud": {
75
+ symbol: "ELF",
76
+ providerId: "gloomberb-cloud",
77
+ dataSource: "delayed",
78
+ price: 88,
79
+ currency: "USD",
80
+ change: 0,
81
+ changePercent: 0,
82
+ lastUpdated: Date.parse("2026-04-08T10:58:00Z"),
83
+ listingExchangeName: "NYSE",
84
+ marketState: "PRE",
85
+ sessionConfidence: "derived",
86
+ preMarketPrice: 89,
87
+ },
88
+ yahoo: {
89
+ symbol: "ELF",
90
+ providerId: "yahoo",
91
+ dataSource: "yahoo",
92
+ price: 87.5,
93
+ currency: "USD",
94
+ change: 0,
95
+ changePercent: 0,
96
+ lastUpdated: Date.parse("2026-04-08T10:57:00Z"),
97
+ listingExchangeName: "NYQ",
98
+ marketState: "PRE",
99
+ sessionConfidence: "derived",
100
+ preMarketPrice: 87.8,
101
+ },
102
+ };
103
+
104
+ const quote = resolveCanonicalQuote(contributions, now).quote;
105
+
106
+ expect(quote?.marketState).toBe("PRE");
107
+ expect(quote?.preMarketPrice).toBe(89);
108
+ expect(quote?.provenance?.session?.providerId).toBe("gloomberb-cloud");
109
+ });
110
+
111
+ test("prefers yahoo extended-hours session data when cloud premarket lacks an active-session price", () => {
112
+ const now = Date.parse("2026-04-08T11:00:00Z");
113
+ const contributions: QuoteContributionMap = {
114
+ "gloomberb-cloud": {
115
+ symbol: "AMD",
116
+ providerId: "gloomberb-cloud",
117
+ dataSource: "delayed",
118
+ price: 221.53,
119
+ currency: "USD",
120
+ change: 1.35,
121
+ changePercent: 0.61,
122
+ lastUpdated: Date.parse("2026-04-08T10:58:00Z"),
123
+ listingExchangeName: "NASDAQ",
124
+ marketState: "PRE",
125
+ sessionConfidence: "derived",
126
+ },
127
+ yahoo: {
128
+ symbol: "AMD",
129
+ providerId: "yahoo",
130
+ dataSource: "yahoo",
131
+ price: 221.53,
132
+ currency: "USD",
133
+ change: 1.35,
134
+ changePercent: 0.61,
135
+ lastUpdated: Date.parse("2026-04-08T10:57:00Z"),
136
+ listingExchangeName: "NMS",
137
+ marketState: "PRE",
138
+ sessionConfidence: "derived",
139
+ preMarketPrice: 231.7,
140
+ preMarketChange: 10.17,
141
+ preMarketChangePercent: 4.59,
142
+ },
143
+ };
144
+
145
+ const quote = resolveCanonicalQuote(contributions, now).quote;
146
+
147
+ expect(quote?.marketState).toBe("PRE");
148
+ expect(quote?.preMarketPrice).toBe(231.7);
149
+ expect(quote?.preMarketChangePercent).toBe(4.59);
150
+ expect(quote?.provenance?.session?.providerId).toBe("yahoo");
151
+ expect(quote?.provenance?.fields?.preMarketPrice?.providerId).toBe("yahoo");
152
+ });
153
+
154
+ test("does not fabricate delayed derived premarket prices from the regular last trade", () => {
155
+ const quote = resolveTickerFinancialsQuoteState({
156
+ annualStatements: [],
157
+ quarterlyStatements: [],
158
+ priceHistory: [],
159
+ quote: {
160
+ symbol: "AMD",
161
+ providerId: "gloomberb-cloud",
162
+ dataSource: "delayed",
163
+ price: 221.53,
164
+ currency: "USD",
165
+ change: 1.35,
166
+ changePercent: 0.61,
167
+ lastUpdated: 1,
168
+ marketState: "PRE",
169
+ sessionConfidence: "derived",
170
+ },
171
+ })?.quote;
172
+
173
+ expect(quote?.marketState).toBe("PRE");
174
+ expect(quote?.preMarketPrice).toBeUndefined();
175
+ expect(quote?.preMarketChange).toBeUndefined();
176
+ expect(quote?.preMarketChangePercent).toBeUndefined();
177
+ });
178
+
179
+ test("ignores stale cloud price contributions when a fresh yahoo quote exists", () => {
180
+ const now = Date.parse("2026-04-08T10:30:00Z");
181
+ const contributions: QuoteContributionMap = {
182
+ "gloomberb-cloud": {
183
+ symbol: "HY9H",
184
+ providerId: "gloomberb-cloud",
185
+ dataSource: "delayed",
186
+ price: 528,
187
+ currency: "EUR",
188
+ change: 4,
189
+ changePercent: 0.76,
190
+ lastUpdated: Date.parse("2026-04-07T17:55:00Z"),
191
+ listingExchangeName: "FWB2",
192
+ marketState: "REGULAR",
193
+ sessionConfidence: "explicit",
194
+ },
195
+ yahoo: {
196
+ symbol: "HY9H",
197
+ providerId: "yahoo",
198
+ dataSource: "yahoo",
199
+ price: 598,
200
+ currency: "EUR",
201
+ change: 6,
202
+ changePercent: 1.01,
203
+ lastUpdated: Date.parse("2026-04-08T10:25:00Z"),
204
+ listingExchangeName: "FWB2",
205
+ marketState: "REGULAR",
206
+ sessionConfidence: "derived",
207
+ },
208
+ };
209
+
210
+ const quote = resolveCanonicalQuote(contributions, now).quote;
211
+
212
+ expect(quote?.price).toBe(598);
213
+ expect(quote?.providerId).toBe("yahoo");
214
+ expect(quote?.provenance?.price?.providerId).toBe("yahoo");
215
+ });
216
+
217
+ test("rejects an incoming stale cloud contribution when a fresh quote already exists", () => {
218
+ const now = Date.parse("2026-04-08T10:30:00Z");
219
+ const current: QuoteContributionMap = {
220
+ yahoo: {
221
+ symbol: "HY9H",
222
+ providerId: "yahoo",
223
+ dataSource: "yahoo",
224
+ price: 598,
225
+ currency: "EUR",
226
+ change: 6,
227
+ changePercent: 1.01,
228
+ lastUpdated: Date.parse("2026-04-08T10:25:00Z"),
229
+ listingExchangeName: "FWB2",
230
+ marketState: "REGULAR",
231
+ sessionConfidence: "derived",
232
+ },
233
+ };
234
+
235
+ const next = upsertQuoteContributionMap(current, {
236
+ symbol: "HY9H",
237
+ providerId: "gloomberb-cloud",
238
+ dataSource: "delayed",
239
+ price: 528,
240
+ currency: "EUR",
241
+ change: 4,
242
+ changePercent: 0.76,
243
+ lastUpdated: Date.parse("2026-04-07T17:55:00Z"),
244
+ listingExchangeName: "FWB2",
245
+ marketState: "REGULAR",
246
+ sessionConfidence: "explicit",
247
+ }, { now });
248
+
249
+ expect(next).toEqual(current);
250
+ });
251
+
252
+ test("keeps broker-only SMART quotes as unknown session while preserving the route", () => {
253
+ const financials = resolveTickerFinancialsQuoteState({
254
+ annualStatements: [],
255
+ quarterlyStatements: [],
256
+ priceHistory: [],
257
+ quote: {
258
+ symbol: "MU",
259
+ providerId: "ibkr",
260
+ dataSource: "live",
261
+ price: 120,
262
+ currency: "USD",
263
+ change: 1,
264
+ changePercent: 0.84,
265
+ lastUpdated: 1,
266
+ listingExchangeName: "NASDAQ",
267
+ routingExchangeName: "SMART",
268
+ sessionConfidence: "unknown",
269
+ },
270
+ });
271
+
272
+ expect(financials?.quote?.listingExchangeName).toBe("NASDAQ");
273
+ expect(financials?.quote?.routingExchangeName).toBe("SMART");
274
+ expect(financials?.quote?.marketState).toBeUndefined();
275
+ expect(financials?.quote?.provenance?.price?.providerId).toBe("ibkr");
276
+ });
277
+ });