gloomberb 0.3.3 → 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 (143) hide show
  1. package/README.md +27 -16
  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-renderer.ts +7 -2
  17. package/src/components/chart/chart-resolution.test.ts +56 -0
  18. package/src/components/chart/chart-resolution.ts +362 -0
  19. package/src/components/chart/chart-types.ts +12 -4
  20. package/src/components/chart/chart-viewport.test.ts +25 -0
  21. package/src/components/chart/chart-viewport.ts +28 -0
  22. package/src/components/chart/chart.test.ts +26 -0
  23. package/src/components/chart/comparison-chart-data.ts +20 -27
  24. package/src/components/chart/comparison-stock-chart.tsx +374 -99
  25. package/src/components/chart/stock-chart-renderer-switch.test.tsx +124 -0
  26. package/src/components/chart/stock-chart.tsx +484 -112
  27. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
  28. package/src/components/command-bar/command-bar.test.tsx +650 -5
  29. package/src/components/command-bar/command-bar.tsx +359 -63
  30. package/src/components/command-bar/command-registry.ts +8 -0
  31. package/src/components/command-bar/helpers.test.ts +23 -1
  32. package/src/components/command-bar/helpers.ts +14 -5
  33. package/src/components/command-bar/pane-template-display.ts +12 -0
  34. package/src/components/command-bar/root-shortcuts.ts +2 -1
  35. package/src/components/command-bar/workflow-ops.ts +4 -2
  36. package/src/components/detail-feed-view.test.tsx +124 -49
  37. package/src/components/detail-feed-view.tsx +396 -160
  38. package/src/components/layout/floating-pane.tsx +1 -1
  39. package/src/components/layout/pane-header.tsx +26 -2
  40. package/src/components/layout/shell.tsx +62 -50
  41. package/src/components/layout/status-bar.test.tsx +66 -0
  42. package/src/components/layout/status-bar.tsx +26 -5
  43. package/src/components/onboarding/onboarding-wizard.test.tsx +65 -0
  44. package/src/components/onboarding/onboarding-wizard.tsx +5 -6
  45. package/src/components/ticker-list-table.test.tsx +83 -0
  46. package/src/components/ticker-list-table.tsx +174 -97
  47. package/src/components/ui/data-table.tsx +27 -2
  48. package/src/components/ui/index.ts +1 -0
  49. package/src/components/ui/page-stack-view.tsx +60 -0
  50. package/src/components/use-double-click-activation.ts +31 -0
  51. package/src/index.tsx +10 -10
  52. package/src/market-data/coordinator.test.ts +92 -4
  53. package/src/market-data/coordinator.ts +32 -5
  54. package/src/market-data/hooks.test.tsx +120 -0
  55. package/src/market-data/hooks.tsx +53 -16
  56. package/src/market-data/request-types.ts +4 -2
  57. package/src/market-data/selectors.ts +24 -58
  58. package/src/plugins/builtin/chat-controller.test.ts +370 -3
  59. package/src/plugins/builtin/chat-controller.ts +310 -35
  60. package/src/plugins/builtin/chat.test.tsx +118 -6
  61. package/src/plugins/builtin/chat.tsx +175 -116
  62. package/src/plugins/builtin/comparison-chart.test.tsx +66 -5
  63. package/src/plugins/builtin/comparison-chart.tsx +21 -1
  64. package/src/plugins/builtin/news.tsx +1 -11
  65. package/src/plugins/builtin/portfolio-list/cli/commands.ts +656 -0
  66. package/src/plugins/builtin/portfolio-list/command-bar.ts +122 -0
  67. package/src/plugins/builtin/portfolio-list/header.tsx +16 -34
  68. package/src/plugins/builtin/portfolio-list/index.tsx +5 -0
  69. package/src/plugins/builtin/portfolio-list/metrics.test.ts +61 -0
  70. package/src/plugins/builtin/portfolio-list/metrics.ts +2 -2
  71. package/src/plugins/builtin/portfolio-list/mutations.test.ts +187 -0
  72. package/src/plugins/builtin/portfolio-list/mutations.ts +226 -0
  73. package/src/plugins/builtin/portfolio-list/pane.tsx +39 -17
  74. package/src/plugins/builtin/portfolio-list/table.tsx +11 -2
  75. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +163 -0
  76. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +81 -0
  77. package/src/plugins/builtin/sec.tsx +1 -11
  78. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +13 -2
  79. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +21 -0
  80. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +45 -10
  81. package/src/plugins/builtin/ticker-detail/pane.tsx +35 -17
  82. package/src/plugins/builtin/ticker-detail/settings.ts +19 -1
  83. package/src/plugins/builtin/ticker-detail.test.tsx +47 -0
  84. package/src/plugins/catalog.ts +65 -0
  85. package/src/plugins/ibkr/broker-adapter.ts +28 -0
  86. package/src/plugins/ibkr/gateway-service.test.ts +75 -1
  87. package/src/plugins/ibkr/gateway-service.ts +179 -9
  88. package/src/plugins/pane-manager.test.ts +1 -1
  89. package/src/plugins/plugin-runtime.tsx +27 -7
  90. package/src/plugins/prediction-markets/chart.test.tsx +139 -0
  91. package/src/plugins/prediction-markets/chart.tsx +65 -436
  92. package/src/plugins/prediction-markets/cli.ts +28 -0
  93. package/src/plugins/prediction-markets/controller-data.ts +17 -5
  94. package/src/plugins/prediction-markets/controller.ts +141 -121
  95. package/src/plugins/prediction-markets/detail/outcomes.tsx +2 -6
  96. package/src/plugins/prediction-markets/detail/pane.tsx +4 -25
  97. package/src/plugins/prediction-markets/detail/shared.tsx +0 -1
  98. package/src/plugins/prediction-markets/detail.test.tsx +4 -0
  99. package/src/plugins/prediction-markets/index.tsx +2 -0
  100. package/src/plugins/prediction-markets/launch.ts +51 -16
  101. package/src/plugins/prediction-markets/metrics.ts +9 -5
  102. package/src/plugins/prediction-markets/pane.test.tsx +160 -58
  103. package/src/plugins/prediction-markets/pane.tsx +74 -183
  104. package/src/plugins/prediction-markets/table.tsx +6 -3
  105. package/src/plugins/prediction-markets/types.ts +0 -1
  106. package/src/sources/gloomberb-cloud.test.ts +29 -0
  107. package/src/sources/gloomberb-cloud.ts +89 -1
  108. package/src/sources/provider-router.test.ts +308 -3
  109. package/src/sources/provider-router.ts +247 -17
  110. package/src/sources/yahoo-finance.test.ts +26 -0
  111. package/src/sources/yahoo-finance.ts +54 -3
  112. package/src/state/app-context-selector.test.tsx +99 -0
  113. package/src/state/app-context.test.ts +18 -0
  114. package/src/state/app-context.tsx +170 -65
  115. package/src/state/selectors.ts +25 -0
  116. package/src/theme/colors.ts +8 -8
  117. package/src/theme/themes.test.ts +2 -0
  118. package/src/theme/themes.ts +22 -10
  119. package/src/types/broker.ts +21 -0
  120. package/src/types/config.ts +17 -1
  121. package/src/types/data-provider.ts +18 -0
  122. package/src/types/financials.ts +31 -1
  123. package/src/types/plugin.ts +70 -0
  124. package/src/utils/api-client.test.ts +57 -0
  125. package/src/utils/api-client.ts +35 -12
  126. package/src/utils/back-navigation.ts +27 -0
  127. package/src/utils/format.test.ts +14 -0
  128. package/src/utils/format.ts +3 -3
  129. package/src/utils/market-status.ts +46 -1
  130. package/src/utils/price-history.test.ts +25 -0
  131. package/src/utils/price-history.ts +29 -5
  132. package/src/utils/quote-freshness.ts +127 -0
  133. package/src/utils/quote-resolution.test.ts +277 -0
  134. package/src/utils/quote-resolution.ts +641 -0
  135. package/src/utils/slugify.ts +3 -0
  136. package/src/utils/timestamp.ts +25 -0
  137. package/src/version.ts +1 -1
  138. package/src/cli/commands/portfolio.ts +0 -210
  139. package/src/cli/commands/watchlist.ts +0 -210
  140. package/src/plugins/builtin/data-providers.ts +0 -16
  141. package/src/sources/exchange-rates.ts +0 -18
  142. package/src/utils/ascii-chart.ts +0 -172
  143. package/src/utils/ticker-tokenizer.test.ts +0 -48
@@ -632,7 +632,7 @@ describe("ProviderRouter", () => {
632
632
  persistence.close();
633
633
  });
634
634
 
635
- test("prefers fallback price data when broker and provider differ by a 100x unit mismatch", async () => {
635
+ test("keeps broker price authority when a fallback quote is off by a likely 100x unit mismatch", async () => {
636
636
  const router = new ProviderRouter({
637
637
  ...fallbackProvider,
638
638
  async getTickerFinancials() {
@@ -642,11 +642,13 @@ describe("ProviderRouter", () => {
642
642
  priceHistory: [{ date: new Date("2026-03-28T00:00:00Z"), close: 0.245 }],
643
643
  quote: {
644
644
  symbol: "IQE.L",
645
+ providerId: "yahoo",
645
646
  price: 0.245,
646
647
  currency: "GBP",
647
648
  change: -0.021,
648
649
  changePercent: -7.89,
649
650
  lastUpdated: Date.now(),
651
+ dataSource: "yahoo",
650
652
  },
651
653
  fundamentals: { revenue: 1000 },
652
654
  };
@@ -669,11 +671,13 @@ describe("ProviderRouter", () => {
669
671
  priceHistory: [{ date: new Date("2026-03-28T00:00:00Z"), close: 24.5 }],
670
672
  quote: {
671
673
  symbol: "IQE",
674
+ providerId: "ibkr",
672
675
  price: 24.5,
673
676
  currency: "GBP",
674
677
  change: -2.1,
675
678
  changePercent: -7.89,
676
679
  lastUpdated: Date.now(),
680
+ dataSource: "live",
677
681
  },
678
682
  fundamentals: { netIncome: 200 },
679
683
  };
@@ -718,7 +722,8 @@ describe("ProviderRouter", () => {
718
722
  brokerInstanceId: "ibkr-work",
719
723
  });
720
724
 
721
- expect(merged.quote?.price).toBe(0.245);
725
+ expect(merged.quote?.price).toBe(24.5);
726
+ expect(merged.quote?.provenance?.rejectedPriceProviders).toContain("yahoo");
722
727
  expect(merged.priceHistory[0]?.close).toBe(0.245);
723
728
  expect(merged.fundamentals?.netIncome).toBe(200);
724
729
  });
@@ -1071,11 +1076,13 @@ describe("ProviderRouter", () => {
1071
1076
  priceHistory: [],
1072
1077
  quote: {
1073
1078
  symbol: "IQE.L",
1079
+ providerId: "yahoo",
1074
1080
  price: 0.245,
1075
1081
  currency: "GBP",
1076
1082
  change: 0,
1077
1083
  changePercent: 0,
1078
1084
  lastUpdated: Date.now(),
1085
+ dataSource: "yahoo",
1079
1086
  },
1080
1087
  };
1081
1088
  },
@@ -1100,11 +1107,13 @@ describe("ProviderRouter", () => {
1100
1107
  priceHistory: [],
1101
1108
  quote: {
1102
1109
  symbol: "IQE",
1110
+ providerId: "ibkr",
1103
1111
  price: 24.5,
1104
1112
  currency: "GBP",
1105
1113
  change: 0,
1106
1114
  changePercent: 0,
1107
1115
  lastUpdated: Date.now(),
1116
+ dataSource: "live",
1108
1117
  },
1109
1118
  };
1110
1119
  },
@@ -1157,7 +1166,7 @@ describe("ProviderRouter", () => {
1157
1166
  brokerId: "ibkr",
1158
1167
  brokerInstanceId: "ibkr-live",
1159
1168
  });
1160
- expect(live.quote?.price).toBe(0.245);
1169
+ expect(live.quote?.price).toBe(24.5);
1161
1170
 
1162
1171
  const flex = await router.getTickerFinancials("IQE", "LSE", {
1163
1172
  brokerId: "ibkr",
@@ -1281,6 +1290,209 @@ describe("ProviderRouter", () => {
1281
1290
  persistence.close();
1282
1291
  });
1283
1292
 
1293
+ test("drops stale cached cloud quotes while preserving cached cloud fundamentals", () => {
1294
+ const dbPath = createTempDbPath("stale-cloud-cached-financials");
1295
+ const persistence = new AppPersistence(dbPath);
1296
+ const now = Date.parse("2026-04-08T10:55:00Z");
1297
+
1298
+ persistence.resources.set(
1299
+ {
1300
+ namespace: "market",
1301
+ kind: "financials",
1302
+ entityKey: "HY9H",
1303
+ variantKey: "exchange=FWB2",
1304
+ sourceKey: "provider:gloomberb-cloud",
1305
+ },
1306
+ {
1307
+ annualStatements: [],
1308
+ quarterlyStatements: [],
1309
+ priceHistory: [],
1310
+ quote: {
1311
+ symbol: "HY9H",
1312
+ price: 528,
1313
+ currency: "EUR",
1314
+ change: 28,
1315
+ changePercent: 5.6,
1316
+ lastUpdated: Date.parse("2026-04-07T19:55:00Z"),
1317
+ marketState: "REGULAR",
1318
+ exchangeName: "FWB2",
1319
+ listingExchangeName: "FWB2",
1320
+ providerId: "gloomberb-cloud",
1321
+ dataSource: "delayed",
1322
+ },
1323
+ fundamentals: {
1324
+ revenue: 1234,
1325
+ },
1326
+ profile: {
1327
+ description: "Cloud profile",
1328
+ },
1329
+ },
1330
+ {
1331
+ cachePolicy: { staleMs: 60_000, expireMs: 60_000 },
1332
+ fetchedAt: now,
1333
+ },
1334
+ );
1335
+ persistence.resources.set(
1336
+ {
1337
+ namespace: "market",
1338
+ kind: "financials",
1339
+ entityKey: "HY9H",
1340
+ variantKey: "exchange=FWB2",
1341
+ sourceKey: "provider:yahoo",
1342
+ },
1343
+ {
1344
+ annualStatements: [],
1345
+ quarterlyStatements: [],
1346
+ priceHistory: [],
1347
+ quote: {
1348
+ symbol: "HY9H.F",
1349
+ price: 596,
1350
+ currency: "EUR",
1351
+ change: 68,
1352
+ changePercent: 12.88,
1353
+ lastUpdated: now,
1354
+ marketState: "REGULAR",
1355
+ exchangeName: "FWB2",
1356
+ listingExchangeName: "FWB2",
1357
+ providerId: "yahoo",
1358
+ dataSource: "yahoo",
1359
+ },
1360
+ },
1361
+ {
1362
+ cachePolicy: { staleMs: 60_000, expireMs: 60_000 },
1363
+ fetchedAt: now,
1364
+ },
1365
+ );
1366
+
1367
+ const router = new ProviderRouter({
1368
+ ...fallbackProvider,
1369
+ id: "yahoo",
1370
+ name: "Yahoo",
1371
+ async getTickerFinancials() {
1372
+ throw new Error("should not fetch financials");
1373
+ },
1374
+ }, [{
1375
+ ...fallbackProvider,
1376
+ id: "gloomberb-cloud",
1377
+ name: "Cloud",
1378
+ priority: 100,
1379
+ async getTickerFinancials() {
1380
+ throw new Error("should not fetch financials");
1381
+ },
1382
+ }], persistence.resources);
1383
+
1384
+ const cached = router.getCachedFinancialsForTargets([{
1385
+ symbol: "HY9H",
1386
+ exchange: "FWB2",
1387
+ }], { allowExpired: true });
1388
+
1389
+ expect(cached.get("HY9H")?.quote?.providerId).toBe("yahoo");
1390
+ expect(cached.get("HY9H")?.quote?.price).toBe(596);
1391
+ expect(cached.get("HY9H")?.fundamentals?.revenue).toBe(1234);
1392
+ expect(cached.get("HY9H")?.profile?.description).toBe("Cloud profile");
1393
+
1394
+ persistence.close();
1395
+ });
1396
+
1397
+ test("drops cached premarket cloud quotes that lack an active-session price", () => {
1398
+ const dbPath = createTempDbPath("stale-cloud-premarket-cache");
1399
+ const persistence = new AppPersistence(dbPath);
1400
+ const now = Date.parse("2026-04-08T10:55:00Z");
1401
+
1402
+ persistence.resources.set(
1403
+ {
1404
+ namespace: "market",
1405
+ kind: "financials",
1406
+ entityKey: "AMD",
1407
+ variantKey: "exchange=NASDAQ",
1408
+ sourceKey: "provider:gloomberb-cloud",
1409
+ },
1410
+ {
1411
+ annualStatements: [],
1412
+ quarterlyStatements: [],
1413
+ priceHistory: [],
1414
+ quote: {
1415
+ symbol: "AMD",
1416
+ price: 221.53,
1417
+ currency: "USD",
1418
+ change: 1.35,
1419
+ changePercent: 0.61,
1420
+ lastUpdated: now,
1421
+ marketState: "PRE",
1422
+ exchangeName: "NASDAQ",
1423
+ listingExchangeName: "NASDAQ",
1424
+ providerId: "gloomberb-cloud",
1425
+ dataSource: "delayed",
1426
+ },
1427
+ },
1428
+ {
1429
+ cachePolicy: { staleMs: 60_000, expireMs: 60_000 },
1430
+ fetchedAt: now,
1431
+ },
1432
+ );
1433
+ persistence.resources.set(
1434
+ {
1435
+ namespace: "market",
1436
+ kind: "financials",
1437
+ entityKey: "AMD",
1438
+ variantKey: "exchange=NASDAQ",
1439
+ sourceKey: "provider:yahoo",
1440
+ },
1441
+ {
1442
+ annualStatements: [],
1443
+ quarterlyStatements: [],
1444
+ priceHistory: [],
1445
+ quote: {
1446
+ symbol: "AMD",
1447
+ price: 221.53,
1448
+ currency: "USD",
1449
+ change: 1.35,
1450
+ changePercent: 0.61,
1451
+ lastUpdated: now,
1452
+ marketState: "PRE",
1453
+ preMarketPrice: 231,
1454
+ preMarketChange: 9.47,
1455
+ preMarketChangePercent: 4.27,
1456
+ exchangeName: "NASDAQ",
1457
+ listingExchangeName: "NASDAQ",
1458
+ providerId: "yahoo",
1459
+ dataSource: "yahoo",
1460
+ },
1461
+ },
1462
+ {
1463
+ cachePolicy: { staleMs: 60_000, expireMs: 60_000 },
1464
+ fetchedAt: now,
1465
+ },
1466
+ );
1467
+
1468
+ const router = new ProviderRouter({
1469
+ ...fallbackProvider,
1470
+ id: "yahoo",
1471
+ name: "Yahoo",
1472
+ async getTickerFinancials() {
1473
+ throw new Error("should not fetch financials");
1474
+ },
1475
+ }, [{
1476
+ ...fallbackProvider,
1477
+ id: "gloomberb-cloud",
1478
+ name: "Cloud",
1479
+ priority: 100,
1480
+ async getTickerFinancials() {
1481
+ throw new Error("should not fetch financials");
1482
+ },
1483
+ }], persistence.resources);
1484
+
1485
+ const cached = router.getCachedFinancialsForTargets([{
1486
+ symbol: "AMD",
1487
+ exchange: "NASDAQ",
1488
+ }], { allowExpired: true });
1489
+
1490
+ expect(cached.get("AMD")?.quote?.providerId).toBe("yahoo");
1491
+ expect(cached.get("AMD")?.quote?.preMarketPrice).toBe(231);
1492
+
1493
+ persistence.close();
1494
+ });
1495
+
1284
1496
  test("does not log expected provider misses for missing chart data", async () => {
1285
1497
  const noisyProvider: DataProvider = {
1286
1498
  ...fallbackProvider,
@@ -1355,6 +1567,49 @@ describe("ProviderRouter", () => {
1355
1567
  expect(history.map((point) => point.close)).toEqual([101, 102, 103]);
1356
1568
  });
1357
1569
 
1570
+ test("ignores poisoned cached chart history and refetches clean data", async () => {
1571
+ const dbPath = createTempDbPath("poisoned-chart-cache");
1572
+ const persistence = new AppPersistence(dbPath);
1573
+
1574
+ persistence.resources.set(
1575
+ {
1576
+ namespace: "market",
1577
+ kind: "price-history",
1578
+ entityKey: "AAPL",
1579
+ variantKey: "exchange=NASDAQ;range=1Y",
1580
+ sourceKey: "provider:yahoo",
1581
+ },
1582
+ [
1583
+ { date: null as any, close: 101 },
1584
+ { date: null as any, close: 102 },
1585
+ ],
1586
+ {
1587
+ cachePolicy: { staleMs: 60_000, expireMs: 60_000 },
1588
+ },
1589
+ );
1590
+
1591
+ let providerCalls = 0;
1592
+ const router = new ProviderRouter({
1593
+ ...fallbackProvider,
1594
+ id: "yahoo",
1595
+ name: "Yahoo",
1596
+ async getPriceHistory() {
1597
+ providerCalls += 1;
1598
+ return [
1599
+ { date: new Date("2026-03-27T00:00:00Z"), close: 201 },
1600
+ { date: new Date("2026-03-28T00:00:00Z"), close: 202 },
1601
+ ];
1602
+ },
1603
+ }, [], persistence.resources);
1604
+
1605
+ const history = await router.getPriceHistory("AAPL", "NASDAQ", "1Y");
1606
+
1607
+ expect(providerCalls).toBe(1);
1608
+ expect(history.map((point) => point.close)).toEqual([201, 202]);
1609
+
1610
+ persistence.close();
1611
+ });
1612
+
1358
1613
  test("bypasses cached financials on explicit refresh requests", async () => {
1359
1614
  const dbPath = createTempDbPath("forced-financial-refresh");
1360
1615
  const persistence = new AppPersistence(dbPath);
@@ -1442,6 +1697,56 @@ describe("ProviderRouter", () => {
1442
1697
  persistence.close();
1443
1698
  });
1444
1699
 
1700
+ test("falls back to later providers for fixed-resolution chart history", async () => {
1701
+ const cloudProvider: DataProvider = {
1702
+ ...fallbackProvider,
1703
+ id: "cloud",
1704
+ name: "Cloud",
1705
+ priority: 100,
1706
+ async getPriceHistoryForResolution() {
1707
+ return [];
1708
+ },
1709
+ };
1710
+ const yahooProvider: DataProvider = {
1711
+ ...fallbackProvider,
1712
+ id: "yahoo",
1713
+ name: "Yahoo",
1714
+ priority: 1000,
1715
+ async getPriceHistoryForResolution() {
1716
+ return [{ date: new Date("2026-03-28T00:00:00Z"), close: 102 }];
1717
+ },
1718
+ };
1719
+
1720
+ const router = new ProviderRouter(yahooProvider, [cloudProvider]);
1721
+ const history = await router.getPriceHistoryForResolution("AAPL", "NASDAQ", "1Y", "1d");
1722
+
1723
+ expect(history[0]?.close).toBe(102);
1724
+ });
1725
+
1726
+ test("returns normalized manual chart resolution capabilities", async () => {
1727
+ const cloudProvider: DataProvider = {
1728
+ ...fallbackProvider,
1729
+ id: "cloud",
1730
+ name: "Cloud",
1731
+ priority: 100,
1732
+ async getChartResolutionCapabilities() {
1733
+ return [];
1734
+ },
1735
+ };
1736
+ const yahooProvider: DataProvider = {
1737
+ ...fallbackProvider,
1738
+ id: "yahoo",
1739
+ name: "Yahoo",
1740
+ priority: 1000,
1741
+ async getChartResolutionCapabilities() {
1742
+ return ["1wk", "auto", "1d", "bogus"];
1743
+ },
1744
+ };
1745
+
1746
+ const router = new ProviderRouter(yahooProvider, [cloudProvider]);
1747
+ expect(await router.getChartResolutionCapabilities("AAPL", "NASDAQ")).toEqual(["1d", "1wk"]);
1748
+ });
1749
+
1445
1750
  test("falls back to later providers when detailed chart history is empty", async () => {
1446
1751
  const cloudProvider: DataProvider = {
1447
1752
  ...fallbackProvider,