gloomberb 0.9.7 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api-client/auth.ts +20 -0
- package/src/api-client/index.test.ts +3 -3
- package/src/api-client/index.ts +48 -6
- package/src/api-client/request.test.ts +63 -0
- package/src/api-client/request.ts +53 -5
- package/src/api-client/types.ts +45 -2
- package/src/app/pane-runtime/layout-placement.ts +0 -1
- package/src/app/runtime/desktop-deeplink.test.ts +87 -0
- package/src/app/runtime/desktop-deeplink.ts +496 -0
- package/src/app.tsx +34 -0
- package/src/brokers/account-cache.ts +2 -10
- package/src/brokers/broker-portfolio-sync.ts +0 -1
- package/src/brokers/broker-ticker-sync.ts +20 -16
- package/src/brokers/remote-broker-adapter.ts +3 -0
- package/src/capabilities/registry.test.ts +25 -0
- package/src/capabilities/registry.ts +22 -5
- package/src/cli/commands/automation.ts +1 -3
- package/src/cli/commands/command-utils.ts +0 -7
- package/src/cli/commands/overview.ts +2 -2
- package/src/cli/commands/plugins.ts +34 -17
- package/src/cli/index.ts +2 -2
- package/src/cli.test.ts +0 -157
- package/src/components/chart/bar-chart-renderer.test.ts +87 -12
- package/src/components/chart/bar-chart-renderer.ts +111 -55
- package/src/components/chart/comparison/renderer.ts +0 -1
- package/src/components/chart/comparison-stock-chart/render-output.ts +3 -8
- package/src/components/chart/comparison-stock-chart/view.tsx +0 -2
- package/src/components/chart/core/index.test.ts +0 -24
- package/src/components/chart/core/renderer.ts +0 -4
- package/src/components/chart/core/resolution.test.ts +0 -17
- package/src/components/chart/core/resolution.ts +0 -26
- package/src/components/chart/core/scroll.test.ts +0 -6
- package/src/components/chart/core/scroll.ts +0 -22
- package/src/components/chart/core/viewport.test.ts +2 -23
- package/src/components/chart/core/viewport.ts +0 -27
- package/src/components/chart/market-session.ts +1 -1
- package/src/components/chart/native/surface/hooks.ts +10 -3
- package/src/components/chart/static/bar-chart-surface.tsx +104 -20
- package/src/components/chart/stock-chart/data-runtime.ts +0 -1
- package/src/components/chart/stock-chart/footer.test.ts +65 -0
- package/src/components/chart/stock-chart/footer.ts +68 -13
- package/src/components/chart/stock-chart/resolved.tsx +18 -4
- package/src/components/chart/stock-chart/viewport/effects.ts +0 -3
- package/src/components/command-bar/helpers.test.ts +1 -80
- package/src/components/command-bar/helpers.ts +1 -36
- package/src/components/command-bar/pane-settings.ts +1 -1
- package/src/components/command-bar/pane-templates/items.ts +11 -3
- package/src/components/command-bar/panel/runtime.ts +0 -2
- package/src/components/command-bar/routes/root/results.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/results.ts +0 -11
- package/src/components/command-bar/surface/index.test.tsx +52 -16
- package/src/components/command-bar/surface/index.tsx +0 -1
- package/src/components/command-bar/workflow/ops.ts +81 -20
- package/src/components/feed-data-table/stack-view.test.tsx +36 -130
- package/src/components/feed-data-table/stack-view.tsx +0 -1
- package/src/components/index.ts +1 -0
- package/src/components/layout/shell/index.test.tsx +2 -113
- package/src/components/layout/shell/index.tsx +0 -1
- package/src/components/layout/shell/menu.ts +0 -1
- package/src/components/layout/shell/terminal-pointer-runtime.ts +0 -1
- package/src/components/onboarding/onboarding-wizard.tsx +3 -3
- package/src/components/onboarding/wizard-keyboard.ts +2 -23
- package/src/components/price-selector-dialog.tsx +3 -3
- package/src/components/ticker/badge/text.tsx +0 -1
- package/src/components/toggle-list.tsx +29 -73
- package/src/components/ui/button.tsx +4 -1
- package/src/components/ui/checkbox.tsx +76 -0
- package/src/components/ui/choice-dialog.tsx +15 -3
- package/src/components/ui/frame.tsx +1 -1
- package/src/components/ui/index.ts +2 -0
- package/src/components/ui/message-composer.tsx +4 -0
- package/src/components/ui/native-select.tsx +4 -2
- package/src/components/ui/page-stack-view.tsx +1 -1
- package/src/components/ui/ui.test.tsx +0 -78
- package/src/core/state/app/layout-reducer.ts +0 -1
- package/src/data/config/layout.ts +3 -3
- package/src/data/resource-store.ts +6 -0
- package/src/data/sqlite/cache.test.ts +5 -0
- package/src/data/ticker-store.ts +0 -7
- package/src/market-data/coordinator/auxiliary.ts +1 -44
- package/src/market-data/coordinator/entries.ts +0 -1
- package/src/market-data/coordinator/events.test.ts +49 -0
- package/src/market-data/coordinator/events.ts +25 -4
- package/src/market-data/coordinator/index.test.ts +223 -10
- package/src/market-data/coordinator/index.ts +41 -21
- package/src/market-data/coordinator/quotes.ts +13 -11
- package/src/market-data/coordinator/subscriptions.test.ts +1 -0
- package/src/market-data/hooks.tsx +17 -13
- package/src/market-data/market/freshness.ts +58 -1
- package/src/market-data/market/status.ts +19 -1
- package/src/market-data/query-store.ts +0 -4
- package/src/market-data/quotes/freshness.test.ts +49 -1
- package/src/market-data/quotes/freshness.ts +8 -0
- package/src/market-data/quotes/resolution.test.ts +185 -0
- package/src/market-data/quotes/resolution.ts +96 -10
- package/src/market-data/request-types.test.ts +61 -1
- package/src/market-data/request-types.ts +42 -8
- package/src/market-data/result-types.ts +0 -11
- package/src/market-data/selectors.ts +1 -5
- package/src/news/aggregator.ts +5 -1
- package/src/pane-settings.test.ts +0 -18
- package/src/plugins/builtin/account-management/footer.ts +2 -5
- package/src/plugins/builtin/account-management/form-components.tsx +175 -59
- package/src/plugins/builtin/account-management/keyboard.ts +32 -2
- package/src/plugins/builtin/account-management/model.test.ts +99 -4
- package/src/plugins/builtin/account-management/model.ts +259 -23
- package/src/plugins/builtin/account-management/pane.tsx +784 -155
- package/src/plugins/builtin/broker-manager/index.test.tsx +0 -32
- package/src/plugins/builtin/buildout/cells.tsx +1 -1
- package/src/plugins/builtin/buildout/detail/satellite-images.tsx +1 -1
- package/src/plugins/builtin/buildout/detail/ui.tsx +0 -1
- package/src/plugins/builtin/buildout/format.ts +2 -11
- package/src/plugins/builtin/chat/chat.test.tsx +127 -1
- package/src/plugins/builtin/chat/content/channel-navigation.ts +1 -21
- package/src/plugins/builtin/chat/content/composer-cursor.ts +29 -0
- package/src/plugins/builtin/chat/content/composer-runtime.ts +22 -24
- package/src/plugins/builtin/chat/content/composer.tsx +84 -1
- package/src/plugins/builtin/chat/content/index.tsx +153 -16
- package/src/plugins/builtin/chat/content/layout-metrics.ts +10 -1
- package/src/plugins/builtin/chat/content/mentions.test.ts +74 -0
- package/src/plugins/builtin/chat/content/mentions.ts +88 -0
- package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +264 -0
- package/src/plugins/builtin/chat/content/shortcuts.ts +47 -1
- package/src/plugins/builtin/chat/content/snapshot.ts +7 -0
- package/src/plugins/builtin/chat/content/transcript.tsx +0 -5
- package/src/plugins/builtin/chat/controller/index.ts +0 -10
- package/src/plugins/builtin/chat/controller.test.ts +5 -5
- package/src/plugins/builtin/chat/message/profile-popover.test.tsx +36 -0
- package/src/plugins/builtin/chat/message/profile-popover.tsx +124 -20
- package/src/plugins/builtin/chat/sidebar.test.tsx +83 -6
- package/src/plugins/builtin/chat/sidebar.tsx +26 -2
- package/src/plugins/builtin/chat/test-harness.tsx +22 -12
- package/src/plugins/builtin/cloud/plugin.tsx +8 -1
- package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -1
- package/src/plugins/builtin/congress-trades/model.ts +2 -7
- package/src/plugins/builtin/congress-trades/pane.tsx +0 -1
- package/src/plugins/builtin/correlation/index.tsx +4 -1
- package/src/plugins/builtin/correlation/relationship/controls.tsx +8 -2
- package/src/plugins/builtin/correlation/relationship/model.ts +22 -2
- package/src/plugins/builtin/correlation/relationship/pane.tsx +9 -3
- package/src/plugins/builtin/earnings/index.tsx +0 -9
- package/src/plugins/builtin/econ/index.tsx +1 -5
- package/src/plugins/builtin/help/index.tsx +2 -2
- package/src/plugins/builtin/holders/pane.tsx +0 -5
- package/src/plugins/builtin/insider/index.tsx +1 -8
- package/src/plugins/builtin/kelly-sizer/index.test.tsx +0 -26
- package/src/plugins/builtin/kelly-sizer/index.tsx +0 -2
- package/src/plugins/builtin/market-movers/index.tsx +0 -2
- package/src/plugins/builtin/news/index.tsx +0 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +0 -11
- package/src/plugins/builtin/options/view.test.tsx +1 -38
- package/src/plugins/builtin/plugin-groups.ts +17 -3
- package/src/plugins/builtin/portfolio-list/column-values.ts +15 -8
- package/src/plugins/builtin/portfolio-list/index.test.ts +52 -25
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +44 -7
- package/src/plugins/builtin/portfolio-list/pane/data.test.ts +54 -0
- package/src/plugins/builtin/portfolio-list/pane/data.ts +50 -3
- package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +314 -16
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -4
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +83 -24
- package/src/plugins/builtin/sec/filing-documents.ts +0 -11
- package/src/plugins/builtin/sec/index.tsx +0 -2
- package/src/plugins/builtin/substack/article-detail.tsx +1 -1
- package/src/plugins/builtin/thirteenf/api.ts +0 -15
- package/src/plugins/builtin/thirteenf/pane.tsx +1 -3
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +42 -6
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +415 -64
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings.ts +23 -2
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/types.ts +8 -1
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +3 -1
- package/src/plugins/builtin/ticker-detail/data-panes.test.ts +95 -1
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +0 -7
- package/src/plugins/builtin/ticker-detail/index.tsx +2 -0
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +5 -34
- package/src/plugins/builtin/world-indices/index.tsx +0 -4
- package/src/plugins/builtin/world-indices/model.ts +0 -7
- package/src/plugins/ibkr/account-cache.ts +1 -1
- package/src/plugins/ibkr/gateway/market-data.ts +12 -5
- package/src/plugins/ibkr/gateway/service/index.test.ts +31 -0
- package/src/plugins/ibkr/portfolio-performance.ts +1 -1
- package/src/plugins/ibkr/trade/utils.ts +1 -7
- package/src/plugins/loader.ts +2 -1
- package/src/plugins/prediction-markets/detail.test.tsx +0 -34
- package/src/plugins/registry/context.ts +15 -0
- package/src/plugins/registry/contributions.ts +22 -11
- package/src/plugins/registry/index.test.ts +44 -0
- package/src/plugins/registry/index.ts +75 -26
- package/src/remote/controller.ts +1 -7
- package/src/remote/json-patch.ts +15 -1
- package/src/remote/server.ts +16 -6
- package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
- package/src/renderers/electrobun/bun/core-capabilities.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +14 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +8 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
- package/src/renderers/electrobun/bun/index.ts +42 -0
- package/src/renderers/electrobun/shared/protocol.ts +5 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +29 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/data-table/dom.ts +0 -1
- package/src/renderers/electrobun/view/desktop/controls.tsx +135 -4
- package/src/renderers/electrobun/view/desktop/list-view.tsx +38 -25
- package/src/renderers/electrobun/view/desktop-deeplink-bridge.ts +10 -0
- package/src/renderers/electrobun/view/host/box.tsx +0 -2
- package/src/renderers/electrobun/view/host/mouse.ts +0 -11
- package/src/renderers/electrobun/view/host/scroll-box.tsx +1 -2
- package/src/renderers/electrobun/view/host/text.tsx +1 -2
- package/src/renderers/electrobun/view/http-fetch.ts +15 -2
- package/src/renderers/electrobun/view/main.tsx +3 -0
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +9 -18
- package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +50 -0
- package/src/renderers/electrobun/view/remote/capability-invoker.ts +36 -0
- package/src/renderers/electrobun/view/ui-host.tsx +2 -0
- package/src/renderers/opentui/host.tsx +11 -3
- package/src/renderers/opentui/input-host.tsx +5 -1
- package/src/sources/gloomberb-cloud/index.test.ts +0 -72
- package/src/sources/gloomberb-cloud/index.ts +1 -30
- package/src/sources/gloomberb-cloud/news.ts +0 -12
- package/src/sources/provider-router/batches.ts +11 -3
- package/src/sources/provider-router/cached-financials.test.ts +123 -0
- package/src/sources/provider-router/financial-routes.ts +77 -5
- package/src/sources/provider-router/financials.test.ts +49 -0
- package/src/sources/provider-router/financials.ts +40 -3
- package/src/sources/provider-router/index.test.ts +314 -0
- package/src/sources/provider-router/primary.ts +17 -3
- package/src/sources/provider-router/search.ts +5 -0
- package/src/sources/provider-router/streaming.ts +6 -1
- package/src/sources/provider-router/supplemental.ts +4 -3
- package/src/sources/sec-edgar.test.ts +42 -0
- package/src/sources/sec-edgar.ts +1 -2
- package/src/sources/yahoo-finance/history.ts +5 -1
- package/src/sources/yahoo-finance/mappers.test.ts +29 -0
- package/src/sources/yahoo-finance/mappers.ts +2 -1
- package/src/sources/yahoo-finance/options.ts +59 -14
- package/src/sources/yahoo-finance/quote-summary.ts +4 -1
- package/src/sources/yahoo-finance/requests.ts +2 -1
- package/src/sources/yahoo-finance/snapshots.ts +13 -2
- package/src/sources/yahoo-finance.test.ts +40 -21
- package/src/sources/yahoo-finance.ts +19 -10
- package/src/state/app/context/index.test.tsx +0 -81
- package/src/state/app/context/selector.test.tsx +6 -2
- package/src/state/hooks/quote-streaming.test.ts +14 -1
- package/src/state/hooks/quote-streaming.ts +5 -4
- package/src/sync/controller.test.ts +31 -0
- package/src/sync/controller.ts +300 -0
- package/src/sync/core-contributors.test.ts +251 -0
- package/src/sync/core-contributors.ts +400 -0
- package/src/sync/profile-analytics.ts +39 -0
- package/src/sync/react.ts +53 -0
- package/src/sync/types.ts +77 -0
- package/src/test-support/data-provider.ts +0 -1
- package/src/types/desktop-deeplink.ts +7 -0
- package/src/types/plugin.ts +3 -0
- package/src/ui/host.tsx +15 -1
- package/src/ui/index.tsx +2 -0
- package/src/updater.test.ts +0 -21
- package/src/updater.ts +20 -16
- package/src/utils/async-deadline.ts +26 -0
- package/src/utils/exchanges.test.ts +11 -1
- package/src/utils/exchanges.ts +5 -1
- package/src/utils/selection-clipboard.ts +2 -2
- package/src/version.ts +1 -1
- package/src/components/layout/pane/header.test.tsx +0 -39
- package/src/plugins/builtin/chat/channels.test.ts +0 -28
- package/src/plugins/builtin/chat/index.tsx +0 -4
- package/src/plugins/builtin/cloud-tweets/index.ts +0 -1
- package/src/plugins/builtin/earnings/index.test.ts +0 -12
- package/src/plugins/builtin/help/index.test.tsx +0 -242
- package/src/plugins/builtin/substack/article-detail.test.tsx +0 -10
- package/src/renderers/electrobun/bun/application-menu/index.test.ts +0 -21
- package/src/renderers/electrobun/bun/window/focus.test.ts +0 -35
- package/src/renderers/electrobun/view/host/mouse.test.ts +0 -37
- package/src/renderers/electrobun/view/host/text.test.ts +0 -22
- package/src/renderers/opentui/dialog-host.test.tsx +0 -44
- /package/src/{plugins/ibkr → utils}/hash.ts +0 -0
|
@@ -279,6 +279,7 @@ function PortfolioHarness({
|
|
|
279
279
|
stateMutator,
|
|
280
280
|
runtime = createTestPluginRuntime(),
|
|
281
281
|
paneHeight = 24,
|
|
282
|
+
paneFocused = true,
|
|
282
283
|
}: {
|
|
283
284
|
config: AppConfig;
|
|
284
285
|
collectionId: string;
|
|
@@ -290,6 +291,7 @@ function PortfolioHarness({
|
|
|
290
291
|
stateMutator?: (state: ReturnType<typeof createInitialState>) => void;
|
|
291
292
|
runtime?: PluginRuntimeAccess;
|
|
292
293
|
paneHeight?: number;
|
|
294
|
+
paneFocused?: boolean;
|
|
293
295
|
}) {
|
|
294
296
|
const initialState = createPortfolioState(config, collectionId, expanded, {
|
|
295
297
|
ticker,
|
|
@@ -309,7 +311,7 @@ function PortfolioHarness({
|
|
|
309
311
|
<PortfolioPane
|
|
310
312
|
paneId={TEST_PANE_ID}
|
|
311
313
|
paneType="portfolio-list"
|
|
312
|
-
focused
|
|
314
|
+
focused={paneFocused}
|
|
313
315
|
width={100}
|
|
314
316
|
height={paneHeight}
|
|
315
317
|
/>
|
|
@@ -323,11 +325,162 @@ async function flushFrame() {
|
|
|
323
325
|
await act(async () => {
|
|
324
326
|
await Promise.resolve();
|
|
325
327
|
await testSetup!.renderOnce();
|
|
328
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
326
329
|
await Promise.resolve();
|
|
327
330
|
await testSetup!.renderOnce();
|
|
328
331
|
});
|
|
329
332
|
}
|
|
330
333
|
|
|
334
|
+
function makeSortWarmupQuote(symbol: string): Quote {
|
|
335
|
+
return makeQuote({
|
|
336
|
+
symbol,
|
|
337
|
+
price: symbol === "SIVE" ? 46.7 : 100,
|
|
338
|
+
change: symbol === "SIVE" ? -9.6 : 0,
|
|
339
|
+
changePercent: symbol === "SIVE" ? -17.05 : 0,
|
|
340
|
+
previousClose: symbol === "SIVE" ? 56.3 : 100,
|
|
341
|
+
listingExchangeName: "NASDAQ",
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function makeSortWarmupBrokerTicker(portfolioId: string, symbol: string, index: number): TickerRecord {
|
|
346
|
+
return makeTicker({
|
|
347
|
+
ticker: symbol,
|
|
348
|
+
name: symbol,
|
|
349
|
+
portfolios: [portfolioId],
|
|
350
|
+
positions: [{
|
|
351
|
+
portfolio: portfolioId,
|
|
352
|
+
shares: 10,
|
|
353
|
+
avgCost: 100,
|
|
354
|
+
currency: "USD",
|
|
355
|
+
broker: "ibkr",
|
|
356
|
+
brokerInstanceId: "ibkr-live",
|
|
357
|
+
brokerAccountId: "DU12345",
|
|
358
|
+
brokerContractId: 10_000 + index,
|
|
359
|
+
markPrice: 100,
|
|
360
|
+
}],
|
|
361
|
+
broker_contracts: [{
|
|
362
|
+
brokerId: "ibkr",
|
|
363
|
+
brokerInstanceId: "ibkr-live",
|
|
364
|
+
symbol,
|
|
365
|
+
localSymbol: symbol,
|
|
366
|
+
exchange: "SMART",
|
|
367
|
+
primaryExchange: "NASDAQ",
|
|
368
|
+
conId: 10_000 + index,
|
|
369
|
+
}],
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async function renderHiddenChangePctSortWarmup(options: { staleCachedSiveSnapshot?: boolean } = {}) {
|
|
374
|
+
const portfolioId = "broker:ibkr-live:DU12345";
|
|
375
|
+
const config = createPortfolioConfigWithColumns(
|
|
376
|
+
portfolioId,
|
|
377
|
+
["ticker", "price", "change_pct", "latency"],
|
|
378
|
+
[createBrokerInstance("gateway", "ibkr-live")],
|
|
379
|
+
);
|
|
380
|
+
const requestedSnapshots: string[] = [];
|
|
381
|
+
const provider: DataProvider = {
|
|
382
|
+
id: "test-provider",
|
|
383
|
+
name: "Test Provider",
|
|
384
|
+
async getTickerFinancials(symbol) {
|
|
385
|
+
requestedSnapshots.push(symbol);
|
|
386
|
+
return {
|
|
387
|
+
annualStatements: [],
|
|
388
|
+
quarterlyStatements: [],
|
|
389
|
+
priceHistory: [],
|
|
390
|
+
quote: makeSortWarmupQuote(symbol),
|
|
391
|
+
};
|
|
392
|
+
},
|
|
393
|
+
async getQuote(symbol) {
|
|
394
|
+
return makeSortWarmupQuote(symbol);
|
|
395
|
+
},
|
|
396
|
+
async getExchangeRate() {
|
|
397
|
+
return 1;
|
|
398
|
+
},
|
|
399
|
+
async search() {
|
|
400
|
+
return [];
|
|
401
|
+
},
|
|
402
|
+
async getArticleSummary() {
|
|
403
|
+
return null;
|
|
404
|
+
},
|
|
405
|
+
async getPriceHistory() {
|
|
406
|
+
return [];
|
|
407
|
+
},
|
|
408
|
+
subscribeQuotes() {
|
|
409
|
+
return () => {};
|
|
410
|
+
},
|
|
411
|
+
};
|
|
412
|
+
sharedCoordinator = new MarketDataCoordinator(provider);
|
|
413
|
+
setSharedMarketDataCoordinator(sharedCoordinator);
|
|
414
|
+
|
|
415
|
+
const tickers = Array.from({ length: 29 }, (_, index) => makeSortWarmupBrokerTicker(portfolioId, `T${String(index).padStart(2, "0")}`, index));
|
|
416
|
+
const sive = makeSortWarmupBrokerTicker(portfolioId, "SIVE", 29);
|
|
417
|
+
if (options.staleCachedSiveSnapshot) {
|
|
418
|
+
const siveInstrument = instrumentFromTicker(sive, "SIVE", { portfolioId });
|
|
419
|
+
if (!siveInstrument) throw new Error("expected SIVE instrument");
|
|
420
|
+
sharedCoordinator.primeCachedFinancials([{
|
|
421
|
+
instrument: siveInstrument,
|
|
422
|
+
financials: {
|
|
423
|
+
annualStatements: [],
|
|
424
|
+
quarterlyStatements: [],
|
|
425
|
+
priceHistory: [],
|
|
426
|
+
quote: makeQuote({
|
|
427
|
+
symbol: "SIVE",
|
|
428
|
+
price: 56.3,
|
|
429
|
+
change: 56.3,
|
|
430
|
+
changePercent: 100,
|
|
431
|
+
previousClose: 0,
|
|
432
|
+
listingExchangeName: "NASDAQ",
|
|
433
|
+
lastUpdated: Date.now() - 24 * 60 * 60_000,
|
|
434
|
+
}),
|
|
435
|
+
},
|
|
436
|
+
}]);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
testSetup = await testRender(
|
|
440
|
+
<PortfolioHarness
|
|
441
|
+
config={config}
|
|
442
|
+
collectionId={portfolioId}
|
|
443
|
+
stateMutator={(state) => {
|
|
444
|
+
state.tickers = new Map([...tickers, sive].map((entry) => [entry.metadata.ticker, entry]));
|
|
445
|
+
state.financials = new Map(tickers.map((entry, index) => [
|
|
446
|
+
entry.metadata.ticker,
|
|
447
|
+
{
|
|
448
|
+
annualStatements: [],
|
|
449
|
+
quarterlyStatements: [],
|
|
450
|
+
priceHistory: [],
|
|
451
|
+
quote: makeQuote({
|
|
452
|
+
symbol: entry.metadata.ticker,
|
|
453
|
+
price: 100 + index,
|
|
454
|
+
change: index,
|
|
455
|
+
changePercent: index,
|
|
456
|
+
listingExchangeName: "NASDAQ",
|
|
457
|
+
}),
|
|
458
|
+
},
|
|
459
|
+
]));
|
|
460
|
+
state.paneState[TEST_PANE_ID] = {
|
|
461
|
+
collectionId: portfolioId,
|
|
462
|
+
cursorSymbol: "T00",
|
|
463
|
+
cashDrawerExpanded: false,
|
|
464
|
+
collectionSorts: {
|
|
465
|
+
[portfolioId]: { columnId: "change_pct", direction: "asc" },
|
|
466
|
+
},
|
|
467
|
+
};
|
|
468
|
+
}}
|
|
469
|
+
paneHeight={12}
|
|
470
|
+
/>,
|
|
471
|
+
{ width: 100, height: 12 },
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
await flushFrame();
|
|
475
|
+
const beforeFrame = testSetup.captureCharFrame();
|
|
476
|
+
await act(async () => {
|
|
477
|
+
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
478
|
+
});
|
|
479
|
+
await flushFrame();
|
|
480
|
+
|
|
481
|
+
return { beforeFrame, frame: testSetup.captureCharFrame(), requestedSnapshots };
|
|
482
|
+
}
|
|
483
|
+
|
|
331
484
|
afterEach(async () => {
|
|
332
485
|
if (testSetup) {
|
|
333
486
|
testSetup.renderer.destroy();
|
|
@@ -991,18 +1144,20 @@ describe("PortfolioListPane cash and margin UI", () => {
|
|
|
991
1144
|
expect(frame).toContain("22.0");
|
|
992
1145
|
});
|
|
993
1146
|
|
|
994
|
-
test("force-refreshes
|
|
1147
|
+
test("force-refreshes old visible quote data even when the pane is not focused", async () => {
|
|
995
1148
|
const config = createPortfolioConfigWithColumns(
|
|
996
1149
|
"broker:ibkr-flex:DU12345",
|
|
997
1150
|
["ticker", "price", "day_pnl"],
|
|
998
1151
|
[createBrokerInstance("flex")],
|
|
999
1152
|
);
|
|
1000
|
-
const
|
|
1153
|
+
const oldQuote = makeQuote({
|
|
1001
1154
|
price: 120,
|
|
1002
|
-
change:
|
|
1003
|
-
changePercent:
|
|
1004
|
-
|
|
1005
|
-
|
|
1155
|
+
change: -5,
|
|
1156
|
+
changePercent: -4,
|
|
1157
|
+
currency: "EUR",
|
|
1158
|
+
listingExchangeName: "FWB2",
|
|
1159
|
+
marketState: "REGULAR",
|
|
1160
|
+
lastUpdated: Date.now() - 5 * 60 * 1000,
|
|
1006
1161
|
});
|
|
1007
1162
|
const batchOptions: Array<{ forceRefresh?: boolean } | undefined> = [];
|
|
1008
1163
|
const provider: DataProvider = {
|
|
@@ -1013,7 +1168,7 @@ describe("PortfolioListPane cash and margin UI", () => {
|
|
|
1013
1168
|
annualStatements: [],
|
|
1014
1169
|
quarterlyStatements: [],
|
|
1015
1170
|
priceHistory: [],
|
|
1016
|
-
quote:
|
|
1171
|
+
quote: oldQuote,
|
|
1017
1172
|
};
|
|
1018
1173
|
},
|
|
1019
1174
|
async getQuotesBatch(targets, options) {
|
|
@@ -1023,12 +1178,11 @@ describe("PortfolioListPane cash and margin UI", () => {
|
|
|
1023
1178
|
quote: makeQuote({
|
|
1024
1179
|
symbol: target.symbol,
|
|
1025
1180
|
price: 126,
|
|
1026
|
-
change:
|
|
1027
|
-
changePercent:
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
preMarketChangePercent: 5,
|
|
1181
|
+
change: 1,
|
|
1182
|
+
changePercent: 0.8,
|
|
1183
|
+
currency: "EUR",
|
|
1184
|
+
listingExchangeName: "FWB2",
|
|
1185
|
+
marketState: "REGULAR",
|
|
1032
1186
|
}),
|
|
1033
1187
|
}));
|
|
1034
1188
|
},
|
|
@@ -1058,14 +1212,25 @@ describe("PortfolioListPane cash and margin UI", () => {
|
|
|
1058
1212
|
<PortfolioHarness
|
|
1059
1213
|
config={config}
|
|
1060
1214
|
collectionId="broker:ibkr-flex:DU12345"
|
|
1061
|
-
|
|
1215
|
+
ticker={makeTicker({ exchange: "FWB2", currency: "EUR" })}
|
|
1216
|
+
quote={oldQuote}
|
|
1217
|
+
paneFocused={false}
|
|
1218
|
+
stateMutator={(state) => {
|
|
1219
|
+
state.focusedPaneId = "portfolio-list:other";
|
|
1220
|
+
state.paneState[TEST_PANE_ID] = {
|
|
1221
|
+
...(state.paneState[TEST_PANE_ID] ?? {}),
|
|
1222
|
+
collectionSorts: {
|
|
1223
|
+
"broker:ibkr-flex:DU12345": { columnId: "ticker", direction: "asc" },
|
|
1224
|
+
},
|
|
1225
|
+
};
|
|
1226
|
+
}}
|
|
1062
1227
|
/>,
|
|
1063
1228
|
{ width: 100, height: 12 },
|
|
1064
1229
|
);
|
|
1065
1230
|
|
|
1066
1231
|
await flushFrame();
|
|
1067
1232
|
await act(async () => {
|
|
1068
|
-
await
|
|
1233
|
+
await Promise.resolve();
|
|
1069
1234
|
});
|
|
1070
1235
|
await flushFrame();
|
|
1071
1236
|
|
|
@@ -1413,4 +1578,137 @@ describe("PortfolioListPane cash and margin UI", () => {
|
|
|
1413
1578
|
expect(frame).toContain("+5.41%");
|
|
1414
1579
|
});
|
|
1415
1580
|
|
|
1581
|
+
test("streams portfolio rows with the active collection broker contract", async () => {
|
|
1582
|
+
const config = createPortfolioConfigWithColumns(
|
|
1583
|
+
"broker:ibkr-live:DU12345",
|
|
1584
|
+
["ticker", "price", "change_pct", "latency"],
|
|
1585
|
+
[createBrokerInstance("gateway", "ibkr-flex"), createBrokerInstance("gateway", "ibkr-live")],
|
|
1586
|
+
);
|
|
1587
|
+
let subscribedTargets: Array<{ symbol: string; context?: { brokerInstanceId?: string; instrument?: unknown } }> = [];
|
|
1588
|
+
const provider: DataProvider = {
|
|
1589
|
+
id: "test-provider",
|
|
1590
|
+
name: "Test Provider",
|
|
1591
|
+
async getTickerFinancials(symbol) {
|
|
1592
|
+
return {
|
|
1593
|
+
annualStatements: [],
|
|
1594
|
+
quarterlyStatements: [],
|
|
1595
|
+
priceHistory: [],
|
|
1596
|
+
quote: makeQuote({ symbol }),
|
|
1597
|
+
};
|
|
1598
|
+
},
|
|
1599
|
+
async getQuote(symbol) {
|
|
1600
|
+
return makeQuote({ symbol });
|
|
1601
|
+
},
|
|
1602
|
+
async getExchangeRate() {
|
|
1603
|
+
return 1;
|
|
1604
|
+
},
|
|
1605
|
+
async search() {
|
|
1606
|
+
return [];
|
|
1607
|
+
},
|
|
1608
|
+
async getArticleSummary() {
|
|
1609
|
+
return null;
|
|
1610
|
+
},
|
|
1611
|
+
async getPriceHistory() {
|
|
1612
|
+
return [];
|
|
1613
|
+
},
|
|
1614
|
+
subscribeQuotes(targets) {
|
|
1615
|
+
subscribedTargets = targets;
|
|
1616
|
+
return () => {};
|
|
1617
|
+
},
|
|
1618
|
+
};
|
|
1619
|
+
sharedCoordinator = new MarketDataCoordinator(provider);
|
|
1620
|
+
setSharedMarketDataCoordinator(sharedCoordinator);
|
|
1621
|
+
|
|
1622
|
+
const flexContract = {
|
|
1623
|
+
brokerId: "ibkr",
|
|
1624
|
+
brokerInstanceId: "ibkr-flex",
|
|
1625
|
+
symbol: "VICR",
|
|
1626
|
+
localSymbol: "VICR",
|
|
1627
|
+
exchange: "NASDAQ",
|
|
1628
|
+
conId: 275759,
|
|
1629
|
+
};
|
|
1630
|
+
const liveContract = {
|
|
1631
|
+
brokerId: "ibkr",
|
|
1632
|
+
brokerInstanceId: "ibkr-live",
|
|
1633
|
+
symbol: "VICR",
|
|
1634
|
+
localSymbol: "VICR",
|
|
1635
|
+
exchange: "NASDAQ",
|
|
1636
|
+
primaryExchange: "NASDAQ",
|
|
1637
|
+
conId: 275759,
|
|
1638
|
+
};
|
|
1639
|
+
|
|
1640
|
+
testSetup = await testRender(
|
|
1641
|
+
<PortfolioHarness
|
|
1642
|
+
config={config}
|
|
1643
|
+
collectionId="broker:ibkr-live:DU12345"
|
|
1644
|
+
stateMutator={(state) => {
|
|
1645
|
+
state.tickers = new Map([["VICR", makeTicker({
|
|
1646
|
+
ticker: "VICR",
|
|
1647
|
+
name: "Vicor",
|
|
1648
|
+
portfolios: ["broker:ibkr-flex:DU12345", "broker:ibkr-live:DU12345"],
|
|
1649
|
+
positions: [
|
|
1650
|
+
{
|
|
1651
|
+
portfolio: "broker:ibkr-flex:DU12345",
|
|
1652
|
+
shares: 170,
|
|
1653
|
+
avgCost: 198,
|
|
1654
|
+
currency: "USD",
|
|
1655
|
+
broker: "ibkr",
|
|
1656
|
+
brokerInstanceId: "ibkr-flex",
|
|
1657
|
+
brokerAccountId: "DU12345",
|
|
1658
|
+
brokerContractId: 275759,
|
|
1659
|
+
},
|
|
1660
|
+
{
|
|
1661
|
+
portfolio: "broker:ibkr-live:DU12345",
|
|
1662
|
+
shares: 350,
|
|
1663
|
+
avgCost: 290,
|
|
1664
|
+
currency: "USD",
|
|
1665
|
+
broker: "ibkr",
|
|
1666
|
+
brokerInstanceId: "ibkr-live",
|
|
1667
|
+
brokerAccountId: "DU12345",
|
|
1668
|
+
brokerContractId: 275759,
|
|
1669
|
+
},
|
|
1670
|
+
],
|
|
1671
|
+
broker_contracts: [flexContract, liveContract],
|
|
1672
|
+
})]]);
|
|
1673
|
+
state.financials = new Map();
|
|
1674
|
+
state.paneState[TEST_PANE_ID] = {
|
|
1675
|
+
collectionId: "broker:ibkr-live:DU12345",
|
|
1676
|
+
cursorSymbol: "VICR",
|
|
1677
|
+
cashDrawerExpanded: false,
|
|
1678
|
+
};
|
|
1679
|
+
}}
|
|
1680
|
+
/>,
|
|
1681
|
+
{ width: 100, height: 12 },
|
|
1682
|
+
);
|
|
1683
|
+
|
|
1684
|
+
await flushFrame();
|
|
1685
|
+
await act(async () => {
|
|
1686
|
+
await Promise.resolve();
|
|
1687
|
+
});
|
|
1688
|
+
|
|
1689
|
+
const vicrTarget = subscribedTargets.find((target) => target.symbol === "VICR");
|
|
1690
|
+
expect(vicrTarget?.context?.brokerInstanceId).toBe("ibkr-live");
|
|
1691
|
+
expect(vicrTarget?.context?.instrument).toEqual(liveContract);
|
|
1692
|
+
});
|
|
1693
|
+
|
|
1694
|
+
test("warms hidden quote-missing rows when sorting by change percent", async () => {
|
|
1695
|
+
const { beforeFrame, frame, requestedSnapshots } = await renderHiddenChangePctSortWarmup();
|
|
1696
|
+
expect(beforeFrame).not.toContain("SIVE");
|
|
1697
|
+
expect(requestedSnapshots).toContain("SIVE");
|
|
1698
|
+
expect(frame).toContain("SIVE");
|
|
1699
|
+
expect(frame).toContain("-17.05%");
|
|
1700
|
+
expect(frame).toContain("46.7");
|
|
1701
|
+
});
|
|
1702
|
+
|
|
1703
|
+
test("force-refreshes hidden stale cached snapshots when sorting by change percent", async () => {
|
|
1704
|
+
const { beforeFrame, frame, requestedSnapshots } = await renderHiddenChangePctSortWarmup({
|
|
1705
|
+
staleCachedSiveSnapshot: true,
|
|
1706
|
+
});
|
|
1707
|
+
expect(beforeFrame).not.toContain("SIVE");
|
|
1708
|
+
expect(requestedSnapshots).toContain("SIVE");
|
|
1709
|
+
expect(frame).toContain("SIVE");
|
|
1710
|
+
expect(frame).toContain("-17.05%");
|
|
1711
|
+
expect(frame).toContain("46.7");
|
|
1712
|
+
});
|
|
1713
|
+
|
|
1416
1714
|
});
|
|
@@ -107,7 +107,10 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
|
|
|
107
107
|
() => getCollectionTickersFromConfig(config, tickersBySymbol, activeCollectionId),
|
|
108
108
|
[activeCollectionId, config, tickersBySymbol],
|
|
109
109
|
);
|
|
110
|
-
const
|
|
110
|
+
const financialsInstrumentOptions = useMemo(() => ({
|
|
111
|
+
portfolioId: isPortfolioTab ? activeCollectionId : undefined,
|
|
112
|
+
}), [activeCollectionId, isPortfolioTab]);
|
|
113
|
+
const marketFinancialsMap = useTickerFinancialsMap(tickers, financialsInstrumentOptions);
|
|
111
114
|
const financialsMap = useMemo(() => {
|
|
112
115
|
const merged = new Map(cachedFinancials);
|
|
113
116
|
for (const [symbol, financials] of marketFinancialsMap) {
|
|
@@ -303,16 +306,20 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
|
|
|
303
306
|
const effectiveStreamWindow = useMemo(() => (
|
|
304
307
|
viewMode === "grid"
|
|
305
308
|
? { start: 0, end: Math.min(sortedTickers.length, 96) }
|
|
306
|
-
:
|
|
307
|
-
|
|
309
|
+
: {
|
|
310
|
+
start: streamWindow.start,
|
|
311
|
+
end: Math.min(sortedTickers.length, Math.max(streamWindow.end, height + 4)),
|
|
312
|
+
}
|
|
313
|
+
), [height, sortedTickers.length, streamWindow, viewMode]);
|
|
308
314
|
|
|
309
315
|
usePortfolioPaneStreaming({
|
|
310
316
|
appActive,
|
|
311
|
-
|
|
317
|
+
activeCollectionId: isPortfolioTab ? activeCollectionId : undefined,
|
|
312
318
|
sortedTickers,
|
|
313
319
|
cursorSymbol,
|
|
314
320
|
streamWindow: effectiveStreamWindow,
|
|
315
321
|
isPortfolioTab,
|
|
322
|
+
activeSort,
|
|
316
323
|
financialsMap,
|
|
317
324
|
visibleWarmupRequirements,
|
|
318
325
|
});
|
|
@@ -4,36 +4,42 @@ import { instrumentFromTicker, quoteSubscriptionTargetFromTicker } from "../../.
|
|
|
4
4
|
import { useQuoteStreaming } from "../../../../state/hooks/quote-streaming";
|
|
5
5
|
import type { TickerFinancials } from "../../../../types/financials";
|
|
6
6
|
import type { TickerRecord } from "../../../../types/ticker";
|
|
7
|
+
import type { CollectionSortPreference } from "../../../../state/app/context";
|
|
7
8
|
import {
|
|
8
9
|
VISIBLE_FINANCIAL_WARMUP_DELAY_MS,
|
|
9
10
|
VISIBLE_QUOTE_REFRESH_COOLDOWN_MS,
|
|
10
11
|
VISIBLE_QUOTE_STREAM_WATCHDOG_MS,
|
|
11
12
|
VISIBLE_SNAPSHOT_WARMUP_BATCH_LIMIT,
|
|
12
13
|
VISIBLE_SNAPSHOT_REFRESH_COOLDOWN_MS,
|
|
14
|
+
SORT_QUOTE_WARMUP_BATCH_LIMIT,
|
|
13
15
|
needsVisibleQuoteWarmup,
|
|
14
16
|
needsVisibleQuoteWatchdogRefresh,
|
|
15
17
|
needsVisibleSnapshotWarmup,
|
|
18
|
+
selectQuoteWarmupTickers,
|
|
16
19
|
selectStreamTickers,
|
|
20
|
+
sortPreferenceUsesQuote,
|
|
17
21
|
visibleWarmupKey,
|
|
18
22
|
type VisibleWarmupRequirements,
|
|
19
23
|
} from "./data";
|
|
20
24
|
|
|
21
25
|
export function usePortfolioPaneStreaming({
|
|
22
26
|
appActive,
|
|
23
|
-
|
|
27
|
+
activeCollectionId,
|
|
24
28
|
sortedTickers,
|
|
25
29
|
cursorSymbol,
|
|
26
30
|
streamWindow,
|
|
27
31
|
isPortfolioTab,
|
|
32
|
+
activeSort,
|
|
28
33
|
financialsMap,
|
|
29
34
|
visibleWarmupRequirements,
|
|
30
35
|
}: {
|
|
31
36
|
appActive: boolean;
|
|
32
|
-
|
|
37
|
+
activeCollectionId?: string;
|
|
33
38
|
sortedTickers: TickerRecord[];
|
|
34
39
|
cursorSymbol: string | null;
|
|
35
40
|
streamWindow: { start: number; end: number };
|
|
36
41
|
isPortfolioTab: boolean;
|
|
42
|
+
activeSort: CollectionSortPreference;
|
|
37
43
|
financialsMap: Map<string, TickerFinancials>;
|
|
38
44
|
visibleWarmupRequirements: VisibleWarmupRequirements;
|
|
39
45
|
}) {
|
|
@@ -57,10 +63,13 @@ export function usePortfolioPaneStreaming({
|
|
|
57
63
|
[streamTickers],
|
|
58
64
|
);
|
|
59
65
|
const streamSurface: "portfolio" | "watchlist" = isPortfolioTab ? "portfolio" : "watchlist";
|
|
66
|
+
const instrumentOptions = useMemo(() => ({
|
|
67
|
+
portfolioId: isPortfolioTab ? activeCollectionId : undefined,
|
|
68
|
+
}), [activeCollectionId, isPortfolioTab]);
|
|
60
69
|
const streamTargets = useMemo(() => (
|
|
61
70
|
sortedTickers
|
|
62
71
|
.map((ticker) => {
|
|
63
|
-
const target = quoteSubscriptionTargetFromTicker(ticker, ticker.metadata.ticker, "provider");
|
|
72
|
+
const target = quoteSubscriptionTargetFromTicker(ticker, ticker.metadata.ticker, "provider", instrumentOptions);
|
|
64
73
|
if (!target) return null;
|
|
65
74
|
const selected = ticker.metadata.ticker === cursorSymbol;
|
|
66
75
|
const visible = priorityStreamSymbols.has(ticker.metadata.ticker);
|
|
@@ -73,32 +82,64 @@ export function usePortfolioPaneStreaming({
|
|
|
73
82
|
};
|
|
74
83
|
})
|
|
75
84
|
.filter((target): target is NonNullable<typeof target> => target != null)
|
|
76
|
-
), [cursorSymbol, priorityStreamSymbols, sortedTickers, streamSurface]);
|
|
85
|
+
), [cursorSymbol, instrumentOptions, priorityStreamSymbols, sortedTickers, streamSurface]);
|
|
77
86
|
const visibleFinancialTickers = useMemo(
|
|
78
87
|
() => sortedTickers.slice(streamWindow.start, streamWindow.end),
|
|
79
88
|
[sortedTickers, streamWindow.end, streamWindow.start],
|
|
80
89
|
);
|
|
90
|
+
const watchdogInputsRef = useRef<{
|
|
91
|
+
financialsMap: Map<string, TickerFinancials>;
|
|
92
|
+
instrumentOptions: typeof instrumentOptions;
|
|
93
|
+
visibleFinancialTickers: TickerRecord[];
|
|
94
|
+
}>({ financialsMap, instrumentOptions, visibleFinancialTickers });
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
watchdogInputsRef.current = {
|
|
98
|
+
financialsMap,
|
|
99
|
+
instrumentOptions,
|
|
100
|
+
visibleFinancialTickers,
|
|
101
|
+
};
|
|
102
|
+
}, [financialsMap, instrumentOptions, visibleFinancialTickers]);
|
|
81
103
|
|
|
82
104
|
useEffect(() => {
|
|
83
105
|
if (!appActive) return;
|
|
84
|
-
if (!focused) return;
|
|
85
106
|
if (!sharedCoordinator) return;
|
|
86
107
|
|
|
87
108
|
const nowTimestamp = Date.now();
|
|
88
109
|
const quoteQueue: TickerRecord[] = [];
|
|
110
|
+
const quoteSnapshotQueue: TickerRecord[] = [];
|
|
89
111
|
const snapshotQueue: TickerRecord[] = [];
|
|
90
|
-
|
|
112
|
+
const snapshotQueueSymbols = new Set<string>();
|
|
113
|
+
const useSnapshotForQuoteWarmup = sortPreferenceUsesQuote(activeSort);
|
|
114
|
+
const quoteWarmupTickers = selectQuoteWarmupTickers(
|
|
115
|
+
sortedTickers,
|
|
116
|
+
streamWindow,
|
|
117
|
+
financialsMap,
|
|
118
|
+
activeSort,
|
|
119
|
+
nowTimestamp,
|
|
120
|
+
);
|
|
121
|
+
for (const ticker of quoteWarmupTickers) {
|
|
91
122
|
const financials = financialsMap.get(ticker.metadata.ticker);
|
|
92
123
|
const quoteKey = visibleWarmupKey("quote", ticker);
|
|
124
|
+
const warmupWithSnapshot = useSnapshotForQuoteWarmup && ticker.metadata.assetCategory !== "OPT";
|
|
125
|
+
const warmupKey = warmupWithSnapshot ? visibleWarmupKey("snapshot", ticker) : quoteKey;
|
|
93
126
|
if (
|
|
94
127
|
needsVisibleQuoteWarmup(financials, nowTimestamp)
|
|
95
|
-
&& !warmupInFlightRef.current.has(
|
|
96
|
-
&& nowTimestamp - (warmupAttemptRef.current.get(
|
|
128
|
+
&& !warmupInFlightRef.current.has(warmupKey)
|
|
129
|
+
&& nowTimestamp - (warmupAttemptRef.current.get(warmupKey) ?? 0) >= VISIBLE_QUOTE_REFRESH_COOLDOWN_MS
|
|
97
130
|
) {
|
|
98
|
-
|
|
99
|
-
|
|
131
|
+
if (warmupWithSnapshot) {
|
|
132
|
+
quoteSnapshotQueue.push(ticker);
|
|
133
|
+
snapshotQueueSymbols.add(ticker.metadata.ticker);
|
|
134
|
+
} else {
|
|
135
|
+
quoteQueue.push(ticker);
|
|
136
|
+
}
|
|
100
137
|
}
|
|
138
|
+
}
|
|
101
139
|
|
|
140
|
+
for (const ticker of visibleFinancialTickers) {
|
|
141
|
+
const financials = financialsMap.get(ticker.metadata.ticker);
|
|
142
|
+
if (snapshotQueueSymbols.has(ticker.metadata.ticker)) continue;
|
|
102
143
|
const snapshotKey = visibleWarmupKey("snapshot", ticker);
|
|
103
144
|
if (
|
|
104
145
|
needsVisibleSnapshotWarmup(ticker, financials, visibleWarmupRequirements)
|
|
@@ -106,43 +147,56 @@ export function usePortfolioPaneStreaming({
|
|
|
106
147
|
&& nowTimestamp - (warmupAttemptRef.current.get(snapshotKey) ?? 0) >= VISIBLE_SNAPSHOT_REFRESH_COOLDOWN_MS
|
|
107
148
|
) {
|
|
108
149
|
snapshotQueue.push(ticker);
|
|
150
|
+
snapshotQueueSymbols.add(ticker.metadata.ticker);
|
|
109
151
|
}
|
|
110
152
|
}
|
|
153
|
+
const limitedQuoteSnapshotQueue = quoteSnapshotQueue.slice(0, SORT_QUOTE_WARMUP_BATCH_LIMIT);
|
|
111
154
|
const limitedSnapshotQueue = snapshotQueue.slice(0, VISIBLE_SNAPSHOT_WARMUP_BATCH_LIMIT);
|
|
112
|
-
if (quoteQueue.length === 0 && limitedSnapshotQueue.length === 0) return;
|
|
155
|
+
if (quoteQueue.length === 0 && limitedQuoteSnapshotQueue.length === 0 && limitedSnapshotQueue.length === 0) return;
|
|
113
156
|
|
|
114
157
|
let cancelled = false;
|
|
115
158
|
const runBatch = async (): Promise<void> => {
|
|
116
159
|
const quoteEntries = quoteQueue.flatMap((ticker) => {
|
|
117
|
-
const instrument = instrumentFromTicker(ticker, ticker.metadata.ticker);
|
|
160
|
+
const instrument = instrumentFromTicker(ticker, ticker.metadata.ticker, instrumentOptions);
|
|
118
161
|
if (!instrument) return [];
|
|
119
162
|
const key = visibleWarmupKey("quote", ticker);
|
|
120
163
|
warmupInFlightRef.current.add(key);
|
|
121
164
|
warmupAttemptRef.current.set(key, nowTimestamp);
|
|
122
165
|
return [{ key, instrument }];
|
|
123
166
|
});
|
|
124
|
-
const
|
|
125
|
-
const instrument = instrumentFromTicker(ticker, ticker.metadata.ticker);
|
|
167
|
+
const forcedSnapshotEntries = limitedQuoteSnapshotQueue.flatMap((ticker) => {
|
|
168
|
+
const instrument = instrumentFromTicker(ticker, ticker.metadata.ticker, instrumentOptions);
|
|
126
169
|
if (!instrument) return [];
|
|
127
170
|
const key = visibleWarmupKey("snapshot", ticker);
|
|
128
171
|
warmupInFlightRef.current.add(key);
|
|
129
172
|
warmupAttemptRef.current.set(key, nowTimestamp);
|
|
130
173
|
return [{ key, instrument }];
|
|
131
174
|
});
|
|
132
|
-
|
|
175
|
+
const normalSnapshotEntries = limitedSnapshotQueue.flatMap((ticker) => {
|
|
176
|
+
const instrument = instrumentFromTicker(ticker, ticker.metadata.ticker, instrumentOptions);
|
|
177
|
+
if (!instrument) return [];
|
|
178
|
+
const key = visibleWarmupKey("snapshot", ticker);
|
|
179
|
+
warmupInFlightRef.current.add(key);
|
|
180
|
+
warmupAttemptRef.current.set(key, nowTimestamp);
|
|
181
|
+
return [{ key, instrument }];
|
|
182
|
+
});
|
|
183
|
+
if (quoteEntries.length === 0 && forcedSnapshotEntries.length === 0 && normalSnapshotEntries.length === 0) return;
|
|
133
184
|
try {
|
|
134
185
|
await Promise.allSettled([
|
|
135
186
|
quoteEntries.length > 0
|
|
136
187
|
? sharedCoordinator.loadQuotesBatch(quoteEntries.map((entry) => entry.instrument), { forceRefresh: true })
|
|
137
188
|
: Promise.resolve(),
|
|
138
|
-
|
|
139
|
-
? sharedCoordinator.loadSnapshotsBatch(
|
|
189
|
+
forcedSnapshotEntries.length > 0
|
|
190
|
+
? sharedCoordinator.loadSnapshotsBatch(forcedSnapshotEntries.map((entry) => entry.instrument), { forceRefresh: true })
|
|
191
|
+
: Promise.resolve(),
|
|
192
|
+
normalSnapshotEntries.length > 0
|
|
193
|
+
? sharedCoordinator.loadSnapshotsBatch(normalSnapshotEntries.map((entry) => entry.instrument))
|
|
140
194
|
: Promise.resolve(),
|
|
141
195
|
]);
|
|
142
196
|
} catch {
|
|
143
197
|
// Best-effort warmup for visible rows only.
|
|
144
198
|
} finally {
|
|
145
|
-
for (const entry of [...quoteEntries, ...
|
|
199
|
+
for (const entry of [...quoteEntries, ...forcedSnapshotEntries, ...normalSnapshotEntries]) {
|
|
146
200
|
warmupInFlightRef.current.delete(entry.key);
|
|
147
201
|
}
|
|
148
202
|
}
|
|
@@ -156,18 +210,22 @@ export function usePortfolioPaneStreaming({
|
|
|
156
210
|
cancelled = true;
|
|
157
211
|
clearTimeout(timeoutId);
|
|
158
212
|
};
|
|
159
|
-
}, [appActive, financialsMap,
|
|
213
|
+
}, [activeSort, appActive, financialsMap, instrumentOptions, sharedCoordinator, sortedTickers, streamWindow, visibleFinancialTickers, visibleWarmupRequirements]);
|
|
160
214
|
|
|
161
215
|
useEffect(() => {
|
|
162
216
|
if (!appActive) return;
|
|
163
|
-
if (!focused) return;
|
|
164
217
|
if (!sharedCoordinator) return;
|
|
165
218
|
|
|
166
219
|
let cancelled = false;
|
|
167
220
|
const runWatchdog = async (): Promise<void> => {
|
|
168
221
|
const nowTimestamp = Date.now();
|
|
169
|
-
const
|
|
170
|
-
|
|
222
|
+
const {
|
|
223
|
+
financialsMap: latestFinancialsMap,
|
|
224
|
+
instrumentOptions: latestInstrumentOptions,
|
|
225
|
+
visibleFinancialTickers: latestVisibleFinancialTickers,
|
|
226
|
+
} = watchdogInputsRef.current;
|
|
227
|
+
const quoteEntries = latestVisibleFinancialTickers.flatMap((ticker) => {
|
|
228
|
+
const financials = latestFinancialsMap.get(ticker.metadata.ticker);
|
|
171
229
|
const key = visibleWarmupKey("quote", ticker);
|
|
172
230
|
if (
|
|
173
231
|
!needsVisibleQuoteWatchdogRefresh(financials, nowTimestamp)
|
|
@@ -176,7 +234,7 @@ export function usePortfolioPaneStreaming({
|
|
|
176
234
|
) {
|
|
177
235
|
return [];
|
|
178
236
|
}
|
|
179
|
-
const instrument = instrumentFromTicker(ticker, ticker.metadata.ticker);
|
|
237
|
+
const instrument = instrumentFromTicker(ticker, ticker.metadata.ticker, latestInstrumentOptions);
|
|
180
238
|
if (!instrument) return [];
|
|
181
239
|
warmupInFlightRef.current.add(key);
|
|
182
240
|
warmupAttemptRef.current.set(key, nowTimestamp);
|
|
@@ -195,6 +253,7 @@ export function usePortfolioPaneStreaming({
|
|
|
195
253
|
}
|
|
196
254
|
};
|
|
197
255
|
|
|
256
|
+
void runWatchdog();
|
|
198
257
|
const intervalId = setInterval(() => {
|
|
199
258
|
if (!cancelled && mountedRef.current) void runWatchdog();
|
|
200
259
|
}, VISIBLE_QUOTE_STREAM_WATCHDOG_MS);
|
|
@@ -203,7 +262,7 @@ export function usePortfolioPaneStreaming({
|
|
|
203
262
|
cancelled = true;
|
|
204
263
|
clearInterval(intervalId);
|
|
205
264
|
};
|
|
206
|
-
}, [appActive,
|
|
265
|
+
}, [appActive, sharedCoordinator]);
|
|
207
266
|
|
|
208
267
|
useQuoteStreaming(streamTargets);
|
|
209
268
|
}
|