gloomberb 0.18.1 → 0.18.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/package.json +1 -1
- package/src/api-client/commodity-balances.ts +6 -0
- package/src/api-client/company-kpis.ts +9 -0
- package/src/api-client/crypto-markets.ts +9 -0
- package/src/api-client/data.ts +6 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/types.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +3 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
- package/src/app/runtime/plugin-bindings.ts +6 -2
- package/src/brokers/logos.ts +66 -0
- package/src/brokers/profile-form.ts +3 -0
- package/src/brokers/remote-broker-adapter.ts +13 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/service-capabilities.ts +7 -1
- package/src/cli/base-converter.ts +60 -18
- package/src/cli/commands/market.ts +281 -154
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +100 -50
- package/src/cli/currency-hints.ts +27 -0
- package/src/cli/fx-pair.ts +41 -4
- package/src/cli/glossary.ts +57 -4
- package/src/cli/helpers.ts +36 -3
- package/src/cli/index.ts +4 -1
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +37 -2
- package/src/cli/pane-functions/catalog.ts +3 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +9 -4
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +27 -3
- package/src/cli/pane-functions/headless.ts +6 -5
- package/src/cli/pane-functions/index.ts +3 -2
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +2 -1
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/series-search.ts +2 -1
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +11 -2
- package/src/components/chart/composite/format.ts +13 -1
- package/src/components/chart/composite/level-labels.ts +30 -0
- package/src/components/chart/composite/levels.ts +17 -6
- package/src/components/chart/composite/panel-surface.tsx +58 -8
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/index.ts +2 -0
- package/src/components/layout/pane/content.tsx +16 -14
- package/src/components/layout/pane/footer/index.tsx +22 -3
- package/src/components/layout/pane/footer/model.ts +99 -4
- package/src/components/layout/pane/header-tabs.tsx +15 -1
- package/src/components/onboarding/onboarding-frame.tsx +9 -11
- package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
- package/src/components/onboarding/wizard-model.ts +10 -3
- package/src/components/ui/button.tsx +11 -4
- package/src/components/ui/choice-dialog.tsx +8 -5
- package/src/components/ui/display.tsx +11 -6
- package/src/components/ui/fields.tsx +100 -20
- package/src/components/ui/icon.tsx +6 -1
- package/src/components/ui/list-row-lines.tsx +87 -0
- package/src/components/ui/list-view.tsx +14 -0
- package/src/components/ui/logo-mark.tsx +64 -0
- package/src/components/ui/number-step.ts +54 -0
- package/src/components/ui/select-button.tsx +5 -4
- package/src/components/ui/select-field.tsx +3 -3
- package/src/components/ui/status.tsx +17 -3
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- package/src/core/state/app/instrument.ts +8 -3
- package/src/data/config/store/normalize.ts +4 -1
- package/src/data/plugin-cache.ts +32 -6
- package/src/docs/snapshot.json +5 -5
- package/src/i18n/es.ts +9 -3
- package/src/i18n/ja.ts +9 -3
- package/src/i18n/ko.ts +9 -3
- package/src/i18n/zh-cn.ts +10 -4
- package/src/i18n/zh-tw.ts +10 -4
- package/src/market-data/commodity-names.ts +80 -0
- package/src/market-data/coordinator/auxiliary.ts +3 -1
- package/src/market-data/coordinator/chart.ts +3 -1
- package/src/market-data/futures-board.ts +38 -1
- package/src/market-data/futures-contract.ts +91 -0
- package/src/market-data/fx-closes.ts +78 -1
- package/src/market-data/market/change-basis.ts +219 -0
- package/src/market-data/market/format.ts +28 -12
- package/src/market-data/market/freshness.ts +38 -0
- package/src/market-data/market/status.ts +55 -9
- package/src/market-data/market/year-to-date.ts +46 -0
- package/src/market-data/quotes/contributions.ts +12 -0
- package/src/market-data/quotes/delisting.ts +34 -0
- package/src/market-data/quotes/live-valuation.ts +23 -0
- package/src/market-data/quotes/resolution.ts +8 -2
- package/src/market-data/tax-equivalent.ts +10 -2
- package/src/notifications/app-notifier.ts +2 -2
- package/src/notifications/notification-log.ts +8 -6
- package/src/plugins/builtin/alerts/cli.ts +26 -18
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- package/src/plugins/builtin/backtest/window.ts +16 -3
- package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
- package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
- package/src/plugins/builtin/broker-manager/index.tsx +20 -9
- package/src/plugins/builtin/broker-manager/model.ts +5 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
- package/src/plugins/builtin/broker-manager/table.ts +5 -1
- package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
- package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
- package/src/plugins/builtin/broker-trading/command.ts +103 -0
- package/src/plugins/builtin/broker-trading/controller.ts +688 -0
- package/src/plugins/builtin/broker-trading/dock.tsx +182 -0
- package/src/plugins/builtin/broker-trading/headless.ts +76 -0
- package/src/plugins/builtin/broker-trading/index.tsx +77 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
- package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
- package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
- package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
- package/src/plugins/builtin/broker-trading/pane.tsx +505 -0
- package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
- package/src/plugins/builtin/broker-trading/ticket.tsx +573 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
- package/src/plugins/builtin/buildout/format.ts +1 -0
- package/src/plugins/builtin/buildout/model/index.ts +16 -7
- package/src/plugins/builtin/buildout/pane/index.tsx +3 -2
- package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
- package/src/plugins/builtin/chart-composer/headless.ts +2 -1
- package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +31 -11
- package/src/plugins/builtin/chart-composer/presets.ts +2 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
- package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
- package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
- package/src/plugins/builtin/commodity-balances/client.ts +6 -4
- package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
- package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
- package/src/plugins/builtin/commodity-balances/model.ts +22 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +36 -5
- package/src/plugins/builtin/company-kpis/client.ts +3 -2
- package/src/plugins/builtin/company-kpis/headless.ts +5 -7
- package/src/plugins/builtin/company-kpis/model.ts +85 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +26 -12
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cot/index.tsx +1 -1
- package/src/plugins/builtin/cot/model.ts +35 -12
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
- package/src/plugins/builtin/crypto-board/client.ts +33 -2
- package/src/plugins/builtin/crypto-board/headless.ts +5 -2
- package/src/plugins/builtin/crypto-board/model.ts +28 -8
- package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
- package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
- package/src/plugins/builtin/econ/calendar-model.ts +35 -0
- package/src/plugins/builtin/econ/index.tsx +31 -4
- package/src/plugins/builtin/econ/print-watch.ts +279 -0
- package/src/plugins/builtin/equity-screener/headless.ts +20 -5
- package/src/plugins/builtin/equity-screener/model.ts +40 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +24 -21
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
- package/src/plugins/builtin/exposure/client.ts +4 -1
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/exposure/evidence.ts +4 -1
- package/src/plugins/builtin/exposure/holdings.ts +3 -1
- package/src/plugins/builtin/exposure/pane.tsx +37 -16
- package/src/plugins/builtin/filing-events/index.tsx +9 -3
- package/src/plugins/builtin/futures/table.tsx +6 -3
- package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
- package/src/plugins/builtin/insider/base-values.ts +72 -0
- package/src/plugins/builtin/insider/headless.ts +29 -4
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- package/src/plugins/builtin/market-halts/model.ts +31 -6
- package/src/plugins/builtin/market-halts/pane.tsx +3 -0
- package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/view.tsx +77 -22
- package/src/plugins/builtin/options-calculator/pane.tsx +4 -2
- package/src/plugins/builtin/options-scenario/pane.tsx +41 -6
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/render.ts +34 -10
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- package/src/plugins/builtin/primary-dealers/client.ts +137 -62
- package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
- package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
- package/src/plugins/builtin/primary-dealers/model.ts +179 -129
- package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
- package/src/plugins/builtin/research/analyst-model.ts +5 -1
- package/src/plugins/builtin/risk-factors/index.tsx +12 -3
- package/src/plugins/builtin/scanner/feed.ts +6 -1
- package/src/plugins/builtin/scanner/headless.ts +1 -1
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
- package/src/plugins/builtin/shared/free-routes.ts +24 -0
- package/src/plugins/builtin/shared/headless-options.ts +11 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +62 -0
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -2
- package/src/plugins/builtin/themes/model.ts +43 -10
- package/src/plugins/builtin/themes/pane.tsx +8 -6
- package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
- package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
- package/src/plugins/builtin/time-sales/pane.tsx +2 -2
- package/src/plugins/builtin/trace-bonds/client.ts +14 -3
- package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +2 -2
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/compat.ts +3 -0
- package/src/plugins/external-runtime.ts +5 -0
- package/src/plugins/loader.ts +6 -0
- package/src/plugins/plugin-icon.ts +61 -0
- package/src/plugins/registry/host-actions.ts +2 -1
- package/src/plugins/registry/index.ts +9 -1
- package/src/plugins/runtime/context.tsx +5 -0
- package/src/plugins/runtime/index.tsx +3 -0
- package/src/portfolio/column-values.ts +13 -4
- package/src/portfolio/cost-fx.ts +5 -28
- package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
- package/src/public/broker.ts +8 -0
- package/src/public/market-data.ts +5 -0
- package/src/renderers/dom/desktop/control-styles.ts +7 -1
- package/src/renderers/dom/desktop/controls.tsx +39 -7
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/list-view.tsx +51 -19
- package/src/renderers/dom/desktop/menu.tsx +10 -1
- package/src/renderers/dom/desktop/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +6 -0
- package/src/renderers/dom/styles.css +12 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +12 -0
- package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
- package/src/renderers/electrobun/view/external-plugins.ts +1 -0
- package/src/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/index.ts +50 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/provider-errors.ts +3 -1
- package/src/sources/provider-router/history.ts +49 -6
- package/src/sources/provider-router/index.ts +2 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +62 -11
- package/src/sync/local-only.ts +121 -0
- package/src/sync/react.ts +6 -0
- package/src/sync/types.ts +3 -0
- package/src/tickers/listing-fate.ts +27 -8
- package/src/tickers/search/index.ts +24 -3
- package/src/time-series/price-comparison.ts +35 -0
- package/src/time-series/resolve.ts +52 -11
- package/src/time-series/spec.ts +1 -1
- package/src/time-series/types.ts +5 -0
- package/src/types/broker.ts +56 -1
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +5 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +12 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +2 -1
- package/src/utils/currency-units.ts +32 -1
- package/src/utils/dom-screenshot.ts +22 -9
- package/src/utils/exchange-notation.generated.ts +360 -0
- package/src/utils/exchanges.ts +15 -322
- package/src/utils/format.ts +31 -1
- package/src/utils/instrument-identity.ts +38 -1
- package/src/utils/options.ts +46 -1
- package/src/utils/price-history.ts +4 -0
- package/src/utils/reported-money.ts +8 -4
|
@@ -9,11 +9,13 @@ const USER_FEEDS_KEY = "feeds";
|
|
|
9
9
|
const DISABLED_DEFAULT_FEED_IDS_KEY = "disabledDefaultFeedIds";
|
|
10
10
|
|
|
11
11
|
const DEFAULT_FEED_IDS = new Set(DEFAULT_FEEDS.map((feed) => feed.id));
|
|
12
|
+
const UNREADABLE_SETTINGS = "News feed settings could not be read. Saved settings were kept.";
|
|
12
13
|
|
|
13
14
|
export interface NewsFeedSettings {
|
|
14
15
|
userFeeds: RssFeedConfig[];
|
|
15
16
|
disabledDefaultFeedIds: string[];
|
|
16
17
|
needsMigration: boolean;
|
|
18
|
+
error?: string;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export interface UserNewsFeedInput {
|
|
@@ -25,15 +27,15 @@ export interface UserNewsFeedInput {
|
|
|
25
27
|
enabled?: boolean;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
function parseJsonArray(value: unknown): unknown[] {
|
|
30
|
+
function parseJsonArray(value: unknown): unknown[] | null {
|
|
29
31
|
if (Array.isArray(value)) return value;
|
|
30
|
-
if (typeof value !== "string"
|
|
32
|
+
if (typeof value !== "string") return [];
|
|
31
33
|
try {
|
|
32
34
|
const parsed = JSON.parse(value);
|
|
33
35
|
return Array.isArray(parsed) ? parsed : [];
|
|
34
36
|
} catch (error) {
|
|
35
37
|
logFailure("news.feed-config", "Read stored news feed settings failed")(error);
|
|
36
|
-
return
|
|
38
|
+
return null;
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -134,14 +136,20 @@ function sameIdList(raw: unknown, normalized: string[]): boolean {
|
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
export function loadNewsFeedSettings(configState: PluginConfigState): NewsFeedSettings {
|
|
137
|
-
|
|
139
|
+
return parseNewsFeedSettings(configState.get(USER_FEEDS_KEY), configState.get(DISABLED_DEFAULT_FEED_IDS_KEY));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function parseNewsFeedSettings(rawUserFeeds: unknown, rawDisabledIds: unknown): NewsFeedSettings {
|
|
138
143
|
const userFeedValues = parseJsonArray(rawUserFeeds);
|
|
139
|
-
const userFeeds = userFeedValues
|
|
144
|
+
const userFeeds = (userFeedValues ?? [])
|
|
140
145
|
.map(normalizeUserFeed)
|
|
141
146
|
.filter((feed): feed is RssFeedConfig => !!feed);
|
|
142
147
|
|
|
143
|
-
const
|
|
144
|
-
const disabledDefaultFeedIds = normalizeDisabledDefaultFeedIds(
|
|
148
|
+
const disabledIdValues = parseJsonArray(rawDisabledIds);
|
|
149
|
+
const disabledDefaultFeedIds = normalizeDisabledDefaultFeedIds(disabledIdValues ?? []);
|
|
150
|
+
if (userFeedValues === null || disabledIdValues === null) {
|
|
151
|
+
return { userFeeds, disabledDefaultFeedIds, needsMigration: false, error: UNREADABLE_SETTINGS };
|
|
152
|
+
}
|
|
145
153
|
|
|
146
154
|
const needsMigration = (rawUserFeeds !== null && !sameUserFeeds(rawUserFeeds, userFeeds))
|
|
147
155
|
|| (rawDisabledIds !== null && !sameIdList(rawDisabledIds, disabledDefaultFeedIds));
|
|
@@ -149,15 +157,24 @@ export function loadNewsFeedSettings(configState: PluginConfigState): NewsFeedSe
|
|
|
149
157
|
return { userFeeds, disabledDefaultFeedIds, needsMigration };
|
|
150
158
|
}
|
|
151
159
|
|
|
160
|
+
function requireReadableSettings(configState: PluginConfigState): NewsFeedSettings {
|
|
161
|
+
const settings = loadNewsFeedSettings(configState);
|
|
162
|
+
if (settings.error) throw new Error(settings.error);
|
|
163
|
+
return settings;
|
|
164
|
+
}
|
|
165
|
+
|
|
152
166
|
export async function saveNewsFeedSettings(
|
|
153
167
|
configState: PluginConfigState,
|
|
154
168
|
settings: Pick<NewsFeedSettings, "userFeeds" | "disabledDefaultFeedIds">,
|
|
155
169
|
): Promise<void> {
|
|
170
|
+
requireReadableSettings(configState);
|
|
156
171
|
await configState.set(USER_FEEDS_KEY, settings.userFeeds);
|
|
172
|
+
requireReadableSettings(configState);
|
|
157
173
|
await configState.set(DISABLED_DEFAULT_FEED_IDS_KEY, settings.disabledDefaultFeedIds);
|
|
158
174
|
}
|
|
159
175
|
|
|
160
|
-
export function getEnabledNewsFeeds(settings: Pick<NewsFeedSettings, "userFeeds" | "disabledDefaultFeedIds">): RssFeedConfig[] {
|
|
176
|
+
export function getEnabledNewsFeeds(settings: Pick<NewsFeedSettings, "userFeeds" | "disabledDefaultFeedIds" | "error">): RssFeedConfig[] {
|
|
177
|
+
if (settings.error) return [];
|
|
161
178
|
const disabled = new Set(settings.disabledDefaultFeedIds);
|
|
162
179
|
return [
|
|
163
180
|
...DEFAULT_FEEDS.filter((feed) => feed.enabled && !disabled.has(feed.id)),
|
|
@@ -169,7 +186,7 @@ export async function addUserNewsFeed(
|
|
|
169
186
|
configState: PluginConfigState,
|
|
170
187
|
input: UserNewsFeedInput,
|
|
171
188
|
): Promise<RssFeedConfig> {
|
|
172
|
-
const settings =
|
|
189
|
+
const settings = requireReadableSettings(configState);
|
|
173
190
|
const feed = createUserFeed(input);
|
|
174
191
|
const nextUserFeeds = [
|
|
175
192
|
...settings.userFeeds.filter((entry) => entry.id !== feed.id),
|
|
@@ -187,7 +204,7 @@ export async function updateUserNewsFeed(
|
|
|
187
204
|
feedId: string,
|
|
188
205
|
patch: Partial<UserNewsFeedInput>,
|
|
189
206
|
): Promise<RssFeedConfig | null> {
|
|
190
|
-
const settings =
|
|
207
|
+
const settings = requireReadableSettings(configState);
|
|
191
208
|
const existing = settings.userFeeds.find((feed) => feed.id === feedId);
|
|
192
209
|
if (!existing) return null;
|
|
193
210
|
const nextFeed = createUserFeed({
|
|
@@ -206,7 +223,7 @@ export async function removeUserNewsFeed(
|
|
|
206
223
|
configState: PluginConfigState,
|
|
207
224
|
feedId: string,
|
|
208
225
|
): Promise<boolean> {
|
|
209
|
-
const settings =
|
|
226
|
+
const settings = requireReadableSettings(configState);
|
|
210
227
|
const nextUserFeeds = settings.userFeeds.filter((feed) => feed.id !== feedId);
|
|
211
228
|
if (nextUserFeeds.length === settings.userFeeds.length) return false;
|
|
212
229
|
await saveNewsFeedSettings(configState, {
|
|
@@ -222,7 +239,7 @@ export async function setDefaultNewsFeedEnabled(
|
|
|
222
239
|
enabled: boolean,
|
|
223
240
|
): Promise<boolean> {
|
|
224
241
|
if (!DEFAULT_FEED_IDS.has(feedId)) return false;
|
|
225
|
-
const settings =
|
|
242
|
+
const settings = requireReadableSettings(configState);
|
|
226
243
|
const disabled = new Set(settings.disabledDefaultFeedIds);
|
|
227
244
|
if (enabled) disabled.delete(feedId);
|
|
228
245
|
else disabled.add(feedId);
|
|
@@ -26,6 +26,7 @@ import { createRssNewsCapability } from "./rss/source";
|
|
|
26
26
|
import { rssCliCommand } from "./rss/cli";
|
|
27
27
|
import { newsFeedHeadless } from "../headless";
|
|
28
28
|
import type { PaneDef } from "../../../../types/plugin";
|
|
29
|
+
import { logFailure } from "../../../../utils/log-failure";
|
|
29
30
|
|
|
30
31
|
const TopPane = createNewsPresetPane({
|
|
31
32
|
paneKey: "top:curated",
|
|
@@ -195,11 +196,15 @@ export const newsWireModule: PluginModule = {
|
|
|
195
196
|
setup(ctx) {
|
|
196
197
|
const initialSettings = loadNewsFeedSettings(ctx.configState);
|
|
197
198
|
if (initialSettings.needsMigration) {
|
|
198
|
-
void saveNewsFeedSettings(ctx.configState, initialSettings)
|
|
199
|
+
void saveNewsFeedSettings(ctx.configState, initialSettings)
|
|
200
|
+
.catch(logFailure("news.feed-config", "Migrate news feed settings failed"));
|
|
199
201
|
}
|
|
200
202
|
|
|
201
203
|
const source = createRssNewsCapability(
|
|
202
|
-
() =>
|
|
204
|
+
() => {
|
|
205
|
+
const settings = loadNewsFeedSettings(ctx.configState);
|
|
206
|
+
return { feeds: getEnabledNewsFeeds(settings), error: settings.error };
|
|
207
|
+
},
|
|
203
208
|
{ persistence: ctx.persistence },
|
|
204
209
|
);
|
|
205
210
|
ctx.registerCapability(source);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { useCallback } from "react";
|
|
1
|
+
import { useCallback, useMemo } from "react";
|
|
2
2
|
import { Box } from "../../../../../ui";
|
|
3
3
|
import type { NewsArticle, NewsQuery } from "../../../../../news/types";
|
|
4
4
|
import { useLoadNewsStory, useNewsArticles, useNewsTableLoadMore } from "../../../../../news/hooks";
|
|
5
5
|
import type { PaneProps } from "../../../../../types/plugin";
|
|
6
|
-
import { useDebouncedPluginPaneState, usePluginPaneState } from "../../../../runtime";
|
|
6
|
+
import { useDebouncedPluginPaneState, usePluginConfigState, usePluginPaneState } from "../../../../runtime";
|
|
7
7
|
import { PaneStatusBody, QueryBar, type QueryBarSearch } from "../../../../../components";
|
|
8
8
|
import { NewsDetailView, useNewsArticleDetail } from "./detail-view";
|
|
9
9
|
import {
|
|
@@ -18,6 +18,7 @@ import { newsListEmptyCopy } from "../filter-articles";
|
|
|
18
18
|
import { NEWS_LIST_SEARCH_PLACEHOLDER, useNewsListSearch, useNewsListSearchHint } from "./list-search";
|
|
19
19
|
import { newsMutesApplyToFeed, useNewsMuteFilter } from "../mutes";
|
|
20
20
|
import { usePopOutNewsArticle } from "./pop-out";
|
|
21
|
+
import { parseNewsFeedSettings } from "../feed-config";
|
|
21
22
|
|
|
22
23
|
interface NewsArticleStackOptions {
|
|
23
24
|
/** Prefix of every key the pane persists, and of its footer registration. */
|
|
@@ -146,6 +147,13 @@ export function NewsPresetPane({
|
|
|
146
147
|
emptyStateTitle,
|
|
147
148
|
emptyStateHint,
|
|
148
149
|
}: PaneProps & NewsPresetPaneConfig) {
|
|
150
|
+
const [storedFeeds] = usePluginConfigState<unknown>("feeds", null);
|
|
151
|
+
const [storedDisabledIds] = usePluginConfigState<unknown>("disabledDefaultFeedIds", null);
|
|
152
|
+
// The first query can finish before RSS registers, and desktop queries may
|
|
153
|
+
// use Cloud alone. A settings failure must remain visible in either case.
|
|
154
|
+
const settingsError = useMemo(() => query.feed === "latest"
|
|
155
|
+
? parseNewsFeedSettings(storedFeeds, storedDisabledIds).error
|
|
156
|
+
: undefined, [query.feed, storedFeeds, storedDisabledIds]);
|
|
149
157
|
const newsState = useNewsArticles(query);
|
|
150
158
|
const persisted = usePersistedNewsArticles(`${paneKey}:articles`, newsState.articles);
|
|
151
159
|
const articles = useNewsMuteFilter(persisted, newsMutesApplyToFeed(query.feed));
|
|
@@ -154,7 +162,7 @@ export function NewsPresetPane({
|
|
|
154
162
|
// body rather than a definitive empty wire.
|
|
155
163
|
const loading = newsState.phase === "loading"
|
|
156
164
|
|| (newsState.phase === "refreshing" && persisted.length === 0);
|
|
157
|
-
const error = newsState.error;
|
|
165
|
+
const error = settingsError ?? newsState.error;
|
|
158
166
|
const { search, searchQuery, ...stack } = useNewsArticleStack({
|
|
159
167
|
paneKey,
|
|
160
168
|
articles,
|
|
@@ -26,8 +26,9 @@ export const rssCliCommand: CliCommandDef = {
|
|
|
26
26
|
authority: 50,
|
|
27
27
|
enabled: true,
|
|
28
28
|
}]);
|
|
29
|
-
const
|
|
30
|
-
ctx.
|
|
29
|
+
const page = await capability.provider.fetchNewsPage!({ feed: "latest", limit: ctx.cliOptions.limit ?? 20 });
|
|
30
|
+
if (page.error) ctx.fail(page.error);
|
|
31
|
+
ctx.printResult({ data: page.articles.map((article) => ({
|
|
31
32
|
title: article.title,
|
|
32
33
|
source: article.source,
|
|
33
34
|
publishedAt: article.publishedAt.toISOString(),
|
|
@@ -129,11 +129,14 @@ function writeFeedCache(
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
export function createRssNewsCapability(
|
|
132
|
-
feedsOrGetter: RssFeedConfig[] | (() => RssFeedConfig[]),
|
|
132
|
+
feedsOrGetter: RssFeedConfig[] | (() => RssFeedConfig[] | { feeds: RssFeedConfig[]; error?: string }),
|
|
133
133
|
options: RssNewsCapabilityOptions = {},
|
|
134
134
|
): NewsCapability {
|
|
135
135
|
const fetchText = options.fetchText ?? ((url: string) => rssClient.fetch(url));
|
|
136
|
-
const getFeeds = () =>
|
|
136
|
+
const getFeeds = () => {
|
|
137
|
+
const selection = Array.isArray(feedsOrGetter) ? feedsOrGetter : feedsOrGetter();
|
|
138
|
+
return Array.isArray(selection) ? { feeds: selection } : selection;
|
|
139
|
+
};
|
|
137
140
|
|
|
138
141
|
async function fetchFeed(feed: RssFeedConfig): Promise<{ articles: MarketNewsItem[]; failed: boolean }> {
|
|
139
142
|
const freshCache = readFeedCache(options.persistence, feed);
|
|
@@ -153,7 +156,9 @@ export function createRssNewsCapability(
|
|
|
153
156
|
|
|
154
157
|
async function fetchPage(query: NewsQuery) {
|
|
155
158
|
if (!supportsQuery(query)) return { articles: [] };
|
|
156
|
-
const
|
|
159
|
+
const selection = getFeeds();
|
|
160
|
+
if (selection.error) return { articles: [], error: selection.error };
|
|
161
|
+
const feeds = selection.feeds.filter((feed) => feed.enabled);
|
|
157
162
|
const results = await Promise.all(feeds.map(fetchFeed));
|
|
158
163
|
const failed = results.filter((result) => result.failed).length;
|
|
159
164
|
return {
|
|
@@ -170,11 +175,15 @@ export function createRssNewsCapability(
|
|
|
170
175
|
supports: supportsQuery,
|
|
171
176
|
getCachedNews(query: NewsQuery): MarketNewsItem[] {
|
|
172
177
|
if (!supportsQuery(query)) return [];
|
|
173
|
-
const
|
|
178
|
+
const selection = getFeeds();
|
|
179
|
+
if (selection.error) return [];
|
|
180
|
+
const enabledFeeds = selection.feeds.filter((feed) => feed.enabled);
|
|
174
181
|
return enabledFeeds.flatMap((feed) => readFeedCache(options.persistence, feed, { allowExpired: true }) ?? []);
|
|
175
182
|
},
|
|
176
183
|
fetchNewsPage: fetchPage,
|
|
177
184
|
async fetchNews(query: NewsQuery): Promise<MarketNewsItem[]> {
|
|
185
|
+
const error = supportsQuery(query) ? getFeeds().error : undefined;
|
|
186
|
+
if (error) throw new Error(error);
|
|
178
187
|
return (await fetchPage(query)).articles;
|
|
179
188
|
},
|
|
180
189
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import type { PaneFooterSegment, PaneHint } from "../../../components";
|
|
3
|
+
import { delayedFooterSegment } from "../../../components/layout/pane/footer/model";
|
|
3
4
|
import { t, tf } from "../../../i18n";
|
|
4
5
|
import type { OptionsChain } from "../../../types/financials";
|
|
5
6
|
import { formatRelativeTime } from "../../../utils/datetime-format";
|
|
@@ -9,18 +10,18 @@ import { CLOUD_QUOTE_DELAY_MINUTES } from "../../../api-client/plan-access";
|
|
|
9
10
|
import type { OptionQuoteCoverage, OptionQuoteCoverageStatus } from "./live-quotes";
|
|
10
11
|
import { formatOptionPrice, optionSpread, type OptionMarketReference } from "./market-reference";
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (status === "live") return { text: t("real-time options"), tone: "positive" };
|
|
20
|
-
if (status === "mixed") return {
|
|
13
|
+
/**
|
|
14
|
+
* Stream coverage the pane reports once the account is entitled to real-time
|
|
15
|
+
* options. Delayed quotes, all or some, are the footer's clock, its detail on
|
|
16
|
+
* hover.
|
|
17
|
+
*/
|
|
18
|
+
export function optionsCoverageSegment(status: OptionQuoteCoverageStatus, delayLabel?: string): PaneFooterSegment | null {
|
|
19
|
+
const id = "options-access";
|
|
20
|
+
if (status === "live") return { id, parts: [{ text: t("real-time options"), tone: "positive" }] };
|
|
21
|
+
if (status === "mixed") return delayedFooterSegment({ id, detail: t("mixed real-time and delayed options") });
|
|
21
22
|
// The stream connects in the background; nothing to report until it answers.
|
|
22
23
|
if (status === "connecting") return null;
|
|
23
|
-
return {
|
|
24
|
+
return delayedFooterSegment({ id, detail: t("Delayed options quotes, real-time stream unavailable"), delay: delayLabel });
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -82,6 +83,7 @@ export function resolveOptionsDelayLabel(chain: OptionsChain | null | undefined)
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
export function useOptionsAccessFooter({
|
|
86
|
+
broker = false,
|
|
85
87
|
chain,
|
|
86
88
|
error,
|
|
87
89
|
focused,
|
|
@@ -91,6 +93,8 @@ export function useOptionsAccessFooter({
|
|
|
91
93
|
reference,
|
|
92
94
|
spreadColumnVisible,
|
|
93
95
|
}: {
|
|
96
|
+
/** The chain is a broker profile's, quoted by that broker: it has no Gloom Cloud delay to report or upgrade from. */
|
|
97
|
+
broker?: boolean;
|
|
94
98
|
chain: OptionsChain | null | undefined;
|
|
95
99
|
error?: string | null;
|
|
96
100
|
focused: boolean;
|
|
@@ -102,24 +106,25 @@ export function useOptionsAccessFooter({
|
|
|
102
106
|
/** Set while the chain prints a SPRD column, so the status bar stops repeating it. */
|
|
103
107
|
spreadColumnVisible?: boolean;
|
|
104
108
|
}): void {
|
|
109
|
+
const delayLabel = resolveOptionsDelayLabel(chain);
|
|
105
110
|
const { access, hint: upgradeHint, segment } = useCloudAccessFooter({
|
|
106
|
-
delayLabel
|
|
111
|
+
delayLabel,
|
|
112
|
+
degraded: !broker,
|
|
107
113
|
focused,
|
|
108
114
|
segmentId: "options-access",
|
|
109
115
|
placement: "omon-footer",
|
|
110
116
|
shortcutScope: "options:upgrade",
|
|
111
117
|
});
|
|
112
|
-
// Trial accounts stream real-time too, but the countdown is the status worth the row.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
// Trial accounts stream real-time too, but the countdown is the status worth the row. A broker's
|
|
119
|
+
// chain streams from the broker whatever the plan, so its coverage is the status, and its delay
|
|
120
|
+
// is the chain's own, not Gloom Cloud's.
|
|
121
|
+
const proCoverage = broker || (access.hasProAccess && !access.isTrialActive);
|
|
122
|
+
const coverageDelay = broker && !(chain?.delayMinutes && chain.delayMinutes > 0) ? undefined : delayLabel;
|
|
117
123
|
const accessInfo = useMemo<PaneFooterSegment[]>(() => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
124
|
+
const coverage = proCoverage ? optionsCoverageSegment(quoteCoverage.status, coverageDelay) : null;
|
|
125
|
+
if (coverage) return [coverage];
|
|
121
126
|
return segment ? [segment] : [];
|
|
122
|
-
}, [
|
|
127
|
+
}, [coverageDelay, proCoverage, quoteCoverage.status, segment]);
|
|
123
128
|
|
|
124
129
|
// Rebuilt every render so the quote and trade ages keep counting with the
|
|
125
130
|
// stream; the registration compares by value, so only a changed label
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { QuoteSubscriptionTarget } from "../../../types/data-provider";
|
|
2
2
|
import type { OptionContract, OptionsChain, Quote } from "../../../types/financials";
|
|
3
|
-
import { buildQuoteKey, resolveEntryData } from "../../../market-data/selectors";
|
|
3
|
+
import { buildQuoteKey, instrumentFromQuoteTarget, resolveEntryData } from "../../../market-data/selectors";
|
|
4
4
|
import type { QueryEntry } from "../../../market-data/result-types";
|
|
5
5
|
import type { OptionTableRow } from "./types";
|
|
6
6
|
import { isFiniteNumber } from "../../../utils/guards";
|
|
@@ -75,9 +75,20 @@ function normalizeVisibleRange(
|
|
|
75
75
|
return { start, end };
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* The broker profile a chain came from. Its contracts' quotes are asked of the
|
|
80
|
+
* same profile, so a broker that streams options (and no cloud stream) keeps
|
|
81
|
+
* the chain it served current, rather than leaving it to a slower source.
|
|
82
|
+
*/
|
|
83
|
+
export interface OptionQuoteBroker {
|
|
84
|
+
brokerId?: string;
|
|
85
|
+
brokerInstanceId?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
78
88
|
export function buildOptionQuoteTargets(
|
|
79
89
|
rows: readonly OptionTableRow[],
|
|
80
90
|
window: OptionQuoteTargetWindow,
|
|
91
|
+
broker?: OptionQuoteBroker,
|
|
81
92
|
): QuoteSubscriptionTarget[] {
|
|
82
93
|
if (rows.length === 0) return [];
|
|
83
94
|
const visibleRange = normalizeVisibleRange(rows.length, window);
|
|
@@ -101,6 +112,7 @@ export function buildOptionQuoteTargets(
|
|
|
101
112
|
targets.set(symbol, {
|
|
102
113
|
symbol,
|
|
103
114
|
exchange: OPTIONS_QUOTE_EXCHANGE,
|
|
115
|
+
...(broker ? { context: { brokerId: broker.brokerId, brokerInstanceId: broker.brokerInstanceId } } : {}),
|
|
104
116
|
surface: "options",
|
|
105
117
|
visible,
|
|
106
118
|
selected,
|
|
@@ -119,10 +131,12 @@ export function buildOptionQuoteTargets(
|
|
|
119
131
|
return [...targets.values()];
|
|
120
132
|
}
|
|
121
133
|
|
|
122
|
-
export function buildOptionQuoteKey(contractSymbol: string): string {
|
|
134
|
+
export function buildOptionQuoteKey(contractSymbol: string, broker?: OptionQuoteBroker): string {
|
|
123
135
|
return buildQuoteKey({
|
|
124
136
|
symbol: contractSymbol,
|
|
125
137
|
exchange: OPTIONS_QUOTE_EXCHANGE,
|
|
138
|
+
brokerId: broker?.brokerId,
|
|
139
|
+
brokerInstanceId: broker?.brokerInstanceId,
|
|
126
140
|
});
|
|
127
141
|
}
|
|
128
142
|
|
|
@@ -179,17 +193,20 @@ export interface LiveOptionsChain {
|
|
|
179
193
|
}
|
|
180
194
|
|
|
181
195
|
const EMPTY_STRIKES: ReadonlySet<number> = new Set();
|
|
182
|
-
const contractKeyIndexes = new WeakMap<readonly OptionContract[], Map<string, number[]
|
|
196
|
+
const contractKeyIndexes = new WeakMap<readonly OptionContract[], Map<string, Map<string, number[]>>>();
|
|
183
197
|
|
|
184
|
-
function contractKeyIndex(contracts: readonly OptionContract[]): Map<string, number[]> {
|
|
185
|
-
|
|
198
|
+
function contractKeyIndex(contracts: readonly OptionContract[], broker?: OptionQuoteBroker): Map<string, number[]> {
|
|
199
|
+
const byBroker = contractKeyIndexes.get(contracts) ?? new Map<string, Map<string, number[]>>();
|
|
200
|
+
contractKeyIndexes.set(contracts, byBroker);
|
|
201
|
+
const brokerKey = `${broker?.brokerId ?? ""}|${broker?.brokerInstanceId ?? ""}`;
|
|
202
|
+
let index = byBroker.get(brokerKey);
|
|
186
203
|
if (!index) {
|
|
187
204
|
index = new Map();
|
|
188
205
|
contracts.forEach((contract, position) => {
|
|
189
|
-
const key = buildOptionQuoteKey(contract.contractSymbol);
|
|
206
|
+
const key = buildOptionQuoteKey(contract.contractSymbol, broker);
|
|
190
207
|
index!.set(key, [...(index!.get(key) ?? []), position]);
|
|
191
208
|
});
|
|
192
|
-
|
|
209
|
+
byBroker.set(brokerKey, index);
|
|
193
210
|
}
|
|
194
211
|
return index;
|
|
195
212
|
}
|
|
@@ -204,12 +221,13 @@ export function overlayOptionChainQuotes(
|
|
|
204
221
|
chain: OptionsChain,
|
|
205
222
|
quoteEntries: ReadonlyMap<string, QueryEntry<Quote>>,
|
|
206
223
|
freshness: OptionsQuoteFreshness,
|
|
224
|
+
broker?: OptionQuoteBroker,
|
|
207
225
|
): LiveOptionsChain {
|
|
208
226
|
if (quoteEntries.size === 0) return { chain, streamedStrikes: EMPTY_STRIKES, quotedAt: null };
|
|
209
227
|
const streamedStrikes = new Set<number>();
|
|
210
228
|
let quotedAt: number | null = null;
|
|
211
229
|
const overlay = (contracts: OptionContract[]): OptionContract[] => {
|
|
212
|
-
const index = contractKeyIndex(contracts);
|
|
230
|
+
const index = contractKeyIndex(contracts, broker);
|
|
213
231
|
let next: OptionContract[] | null = null;
|
|
214
232
|
for (const [key, entry] of quoteEntries) {
|
|
215
233
|
const positions = index.get(key);
|
|
@@ -242,19 +260,16 @@ export function resolveOptionQuoteCoverage(
|
|
|
242
260
|
quoteEntries: ReadonlyMap<string, QueryEntry<Quote>>,
|
|
243
261
|
freshness: OptionsQuoteFreshness,
|
|
244
262
|
): OptionQuoteCoverage {
|
|
245
|
-
|
|
263
|
+
// Each target's own key: a chain's broker profile is part of it.
|
|
264
|
+
const visibleKeys = new Set(
|
|
246
265
|
targets
|
|
247
|
-
.filter((target) => target.visible === true)
|
|
248
|
-
.map((target) =>
|
|
249
|
-
.filter(Boolean),
|
|
266
|
+
.filter((target) => target.visible === true && target.symbol.trim())
|
|
267
|
+
.map((target) => buildQuoteKey(instrumentFromQuoteTarget(target))),
|
|
250
268
|
);
|
|
251
269
|
let liveCount = 0;
|
|
252
270
|
let fallbackCount = 0;
|
|
253
|
-
for (const
|
|
254
|
-
const quote = freshOptionQuote(
|
|
255
|
-
quoteEntries.get(buildOptionQuoteKey(symbol)),
|
|
256
|
-
freshness,
|
|
257
|
-
);
|
|
271
|
+
for (const key of visibleKeys) {
|
|
272
|
+
const quote = freshOptionQuote(quoteEntries.get(key), freshness);
|
|
258
273
|
if (
|
|
259
274
|
quote?.dataSource === "live"
|
|
260
275
|
&& quote.delivery === "stream"
|
|
@@ -266,7 +281,7 @@ export function resolveOptionQuoteCoverage(
|
|
|
266
281
|
}
|
|
267
282
|
}
|
|
268
283
|
|
|
269
|
-
const totalCount =
|
|
284
|
+
const totalCount = visibleKeys.size;
|
|
270
285
|
const status: OptionQuoteCoverageStatus = totalCount > 0 && liveCount === totalCount
|
|
271
286
|
? "live"
|
|
272
287
|
: liveCount > 0
|