gloomberb 0.16.6 → 0.17.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.
- package/package.json +2 -1
- package/src/api-client/chat-pane-link.ts +47 -0
- package/src/api-client/chat.ts +9 -1
- package/src/api-client/data.ts +37 -1
- package/src/api-client/equity-screener.ts +3 -0
- package/src/api-client/europe-energy.ts +91 -0
- package/src/api-client/index.ts +6 -0
- package/src/api-client/letters.ts +41 -0
- package/src/api-client/normalizers.ts +36 -6
- package/src/api-client/organization-tickers.ts +44 -0
- package/src/api-client/request.ts +3 -1
- package/src/api-client/types.ts +27 -1
- package/src/app/runtime/desktop-deeplink.ts +48 -15
- package/src/architecture/core-builtin-allowlist.json +1 -1
- package/src/architecture/dom-cell-metrics-allowlist.json +28 -0
- package/src/ask-gloom/pane-context.ts +23 -19
- package/src/ask-gloom/pane-request.ts +2 -2
- package/src/cli/commands/fred-series.ts +77 -11
- package/src/cli/commands/market.ts +77 -20
- package/src/cli/commands/overview.ts +29 -20
- package/src/cli/commands/ticker.ts +64 -11
- package/src/cli/desktop-pane-shot-view.ts +114 -0
- package/src/cli/desktop-pane-shot.ts +30 -31
- package/src/cli/entry.ts +7 -0
- package/src/cli/export-source.ts +43 -0
- package/src/cli/freshness.ts +18 -3
- package/src/cli/glossary.ts +17 -0
- package/src/cli/history-rows.ts +174 -13
- package/src/cli/known-gaps.ts +14 -3
- package/src/cli/listing-arg.ts +20 -2
- package/src/cli/listing-suggestion.ts +159 -0
- package/src/cli/native-smoke.ts +13 -0
- package/src/cli/not-a-ticker.ts +13 -1
- package/src/cli/pane-functions/capabilities.ts +16 -8
- package/src/cli/pane-functions/dom.ts +28 -13
- package/src/cli/pane-functions/function-help.ts +51 -6
- package/src/cli/pane-functions/headless.ts +26 -7
- package/src/cli/pane-functions/index.ts +46 -3
- package/src/cli/pane-functions/report-notices.ts +6 -3
- package/src/cli/pane-functions/report.ts +16 -2
- package/src/cli/pane-functions/resolver.ts +7 -4
- package/src/cli/report-tables.ts +31 -7
- package/src/cli/result.ts +54 -11
- package/src/cli/symbol-rows.ts +18 -1
- package/src/components/chart/composite/axis-overlays.tsx +2 -0
- package/src/components/chart/composite/composite-chart.tsx +4 -21
- package/src/components/chart/composite/legend.tsx +10 -6
- package/src/components/chart/composite/price-axis-labels.tsx +1 -0
- package/src/components/command-bar/list/model.ts +2 -0
- package/src/components/command-bar/pane-templates/items.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/actions.ts +1 -0
- package/src/components/command-bar/routes/ticker-search/results.ts +3 -1
- package/src/components/command-bar/workflow/ops.ts +8 -97
- package/src/components/command-bar/workflow/pane-reuse.ts +177 -0
- package/src/components/data-table/view.tsx +1 -0
- package/src/components/feed-data-table/stack-view.tsx +18 -42
- package/src/components/layout/missing-pane.tsx +2 -0
- package/src/components/layout/shell/index.tsx +1 -0
- package/src/components/layout/shell/pane/ask.ts +12 -7
- package/src/components/layout/shell/pane/capture.ts +8 -34
- package/src/components/markdown-text.tsx +37 -6
- package/src/components/reader/article-reader.tsx +91 -266
- package/src/components/reader/detail-reader.tsx +210 -0
- package/src/components/reader/reader-text.tsx +239 -0
- package/src/components/table-view-shared.tsx +8 -4
- package/src/components/ticker/badge/list.tsx +15 -4
- package/src/components/ui/choice-dialog.tsx +3 -0
- package/src/components/ui/data-table/types.ts +7 -0
- package/src/components/ui/detail-scroll-body.tsx +31 -4
- package/src/components/ui/display.tsx +46 -1
- package/src/components/ui/icon.tsx +11 -1
- package/src/components/ui/list-view.tsx +3 -1
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +9 -1
- package/src/components/ui/select-button.tsx +3 -0
- package/src/data/config/store/fold-panes.ts +202 -11
- package/src/data/config/store/migrations.ts +13 -1
- package/src/docs/snapshot.json +29 -0
- package/src/i18n/es.ts +14 -0
- package/src/i18n/ja.ts +14 -0
- package/src/i18n/ko.ts +14 -0
- package/src/i18n/zh-cn.ts +14 -0
- package/src/i18n/zh-tw.ts +14 -0
- package/src/layout/desk-keys.ts +7 -0
- package/src/layout/desks.ts +2 -2
- package/src/market-data/market/status.ts +67 -6
- package/src/market-data/quotes/contributions.ts +15 -0
- package/src/market-data/quotes/regular-session-valuation.ts +131 -0
- package/src/market-data/quotes/resolution.ts +4 -0
- package/src/market-data/snapshot-provider.ts +6 -3
- package/src/notifications/app-badge.ts +30 -0
- package/src/plugins/builtin/account-management/calendar-tab.tsx +1 -1
- package/src/plugins/builtin/account-management/form-components.tsx +53 -1
- package/src/plugins/builtin/account-management/keyboard.ts +5 -0
- package/src/plugins/builtin/account-management/model.ts +27 -0
- package/src/plugins/builtin/account-management/pane.tsx +62 -2
- package/src/plugins/builtin/alerts/cli.ts +6 -4
- package/src/plugins/builtin/analytics/index.tsx +1 -1
- package/src/plugins/builtin/awards/pane.tsx +29 -18
- package/src/plugins/builtin/backtest/pane.tsx +2 -1
- package/src/plugins/builtin/bond-calculator/index.tsx +3 -5
- package/src/plugins/builtin/buildout/detail/company.tsx +7 -4
- package/src/plugins/builtin/builtin-plugin-meta.ts +5 -4
- package/src/plugins/builtin/catalysts/model.ts +92 -10
- package/src/plugins/builtin/catalysts/pane.tsx +6 -4
- package/src/plugins/builtin/cds/index.tsx +21 -30
- package/src/plugins/builtin/changelog/index.tsx +11 -12
- package/src/plugins/builtin/chart-composer/headless-schema.ts +2 -0
- package/src/plugins/builtin/chart-composer/headless.ts +87 -5
- package/src/plugins/builtin/chart-composer/pane.tsx +3 -2
- package/src/plugins/builtin/chart-composer/session-history.ts +20 -5
- package/src/plugins/builtin/chat/command-bar.ts +6 -8
- package/src/plugins/builtin/chat/content/composer-runtime.ts +2 -2
- package/src/plugins/builtin/chat/content/composer.tsx +13 -4
- package/src/plugins/builtin/chat/content/footer.tsx +15 -5
- package/src/plugins/builtin/chat/content/handle-prompt.tsx +326 -0
- package/src/plugins/builtin/chat/content/index.tsx +113 -9
- package/src/plugins/builtin/chat/content/layout-metrics.ts +4 -0
- package/src/plugins/builtin/chat/content/open-targets.ts +20 -4
- package/src/plugins/builtin/chat/content/scroll.ts +6 -1
- package/src/plugins/builtin/chat/content/transcript.tsx +11 -2
- package/src/plugins/builtin/chat/ghost-user.ts +1 -1
- package/src/plugins/builtin/chat/handle-prompt.ts +128 -0
- package/src/plugins/builtin/chat/index.tsx +44 -21
- package/src/plugins/builtin/chat/layout.ts +7 -14
- package/src/plugins/builtin/chat/message/desktop.tsx +59 -11
- package/src/plugins/builtin/chat/message/header.tsx +69 -21
- package/src/plugins/builtin/chat/message/pane-chip.tsx +223 -0
- package/src/plugins/builtin/chat/message/person-icons.tsx +53 -0
- package/src/plugins/builtin/chat/message/profile-popover.tsx +73 -9
- package/src/plugins/builtin/chat/message/render-state.ts +15 -2
- package/src/plugins/builtin/chat/message/terminal.tsx +33 -8
- package/src/plugins/builtin/chat/message/types.ts +3 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +149 -0
- package/src/plugins/builtin/chat/pane-links/open.ts +29 -0
- package/src/plugins/builtin/chat/pane-links/previews.ts +119 -0
- package/src/plugins/builtin/chat/pane-state.ts +5 -8
- package/src/plugins/builtin/chat/people.ts +202 -0
- package/src/plugins/builtin/chat/profile-popover.ts +18 -2
- package/src/plugins/builtin/chat/status-widget.tsx +3 -8
- package/src/plugins/builtin/chat/unread-badge.ts +65 -0
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +9 -6
- package/src/plugins/builtin/clinical-trials/client.ts +26 -1
- package/src/plugins/builtin/clinical-trials/index.tsx +10 -2
- package/src/plugins/builtin/clinical-trials/model.ts +269 -0
- package/src/plugins/builtin/clinical-trials/pane.tsx +247 -62
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +156 -0
- package/src/plugins/builtin/cloud/askg/attached-pane.tsx +3 -3
- package/src/plugins/builtin/cloud/askg/pane-attachment.ts +3 -3
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +1 -0
- package/src/plugins/builtin/comment-letters/pane.tsx +30 -11
- package/src/plugins/builtin/commodities-energy/index.tsx +179 -0
- package/src/plugins/builtin/commodity-balances/client.ts +208 -0
- package/src/plugins/builtin/commodity-balances/headless.ts +66 -0
- package/src/plugins/builtin/commodity-balances/index.tsx +32 -0
- package/src/plugins/builtin/commodity-balances/model.ts +261 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +227 -0
- package/src/plugins/builtin/composite-plugins.ts +26 -1
- package/src/plugins/builtin/credit-boards/index.tsx +21 -42
- package/src/plugins/builtin/credit-boards/model.ts +0 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +10 -19
- package/src/plugins/builtin/credit-documents/index.tsx +27 -10
- package/src/plugins/builtin/debt-maturities/index.tsx +3 -17
- package/src/plugins/builtin/debug/index.tsx +2 -2
- package/src/plugins/builtin/dividend-yield/client.ts +3 -1
- package/src/plugins/builtin/dividend-yield/headless.ts +3 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +3 -1
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/docs/blocks.tsx +4 -3
- package/src/plugins/builtin/docs/pane.tsx +16 -8
- package/src/plugins/builtin/earnings/index.tsx +2 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +2 -12
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +36 -2
- package/src/plugins/builtin/equity-screener/model.ts +121 -68
- package/src/plugins/builtin/executives/pane.tsx +3 -1
- package/src/plugins/builtin/filing-events/index.tsx +20 -0
- package/src/plugins/builtin/fixed-income/index.tsx +46 -0
- package/src/plugins/builtin/fixed-income/model.ts +27 -0
- package/src/plugins/builtin/fixed-income/pane.tsx +162 -0
- package/src/plugins/builtin/fixed-income/shortcut.ts +52 -0
- package/src/plugins/builtin/futures/index.tsx +24 -7
- package/src/plugins/builtin/futures/series.ts +23 -2
- package/src/plugins/builtin/futures/use-front-returns.ts +3 -2
- package/src/plugins/builtin/futures-curve/pane.tsx +6 -4
- package/src/plugins/builtin/help/shortcut-table.tsx +4 -2
- package/src/plugins/builtin/insider/detail.tsx +20 -12
- package/src/plugins/builtin/insider/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/index.tsx +3 -0
- package/src/plugins/builtin/iv-history/screen-pane.tsx +13 -5
- package/src/plugins/builtin/jodi/client.ts +220 -0
- package/src/plugins/builtin/jodi/headless.ts +63 -0
- package/src/plugins/builtin/jodi/index.tsx +32 -0
- package/src/plugins/builtin/jodi/model.ts +75 -0
- package/src/plugins/builtin/jodi/pane.tsx +141 -0
- package/src/plugins/builtin/market-heatmap/evidence.ts +13 -4
- package/src/plugins/builtin/market-heatmap/headless.ts +3 -3
- package/src/plugins/builtin/market-heatmap/index.tsx +41 -23
- package/src/plugins/builtin/market-heatmap/live.ts +2 -1
- package/src/plugins/builtin/market-heatmap/portfolio.ts +67 -21
- package/src/plugins/builtin/market-movers/table.ts +2 -2
- package/src/plugins/builtin/market-valuation/align.ts +10 -9
- package/src/plugins/builtin/mna/distress/model.ts +52 -1
- package/src/plugins/builtin/mna/distress/pane.tsx +17 -12
- package/src/plugins/builtin/mna/index.tsx +2 -0
- package/src/plugins/builtin/news/wire/index.tsx +2 -2
- package/src/plugins/builtin/news/wire/industry-pane.tsx +28 -14
- package/src/plugins/builtin/news/wire/letters/client.ts +108 -0
- package/src/plugins/builtin/news/wire/letters/model.ts +69 -0
- package/src/plugins/builtin/news/wire/letters/pane.tsx +155 -0
- package/src/plugins/builtin/news/wire/mutes.ts +2 -1
- package/src/plugins/builtin/news/wire/news/query-presets.ts +4 -1
- package/src/plugins/builtin/openfda/client.ts +123 -15
- package/src/plugins/builtin/openfda/model.ts +163 -0
- package/src/plugins/builtin/openfda/pane.tsx +109 -65
- package/src/plugins/builtin/openfda/types.ts +25 -0
- package/src/plugins/builtin/options/view.tsx +1 -1
- package/src/plugins/builtin/pe-band/model.ts +3 -2
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +1 -1
- package/src/plugins/builtin/plugin-module.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +1 -1
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +19 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +2 -2
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +39 -0
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +18 -5
- package/src/plugins/builtin/portfolio-list/live-valuation.ts +25 -13
- package/src/plugins/builtin/power/evidence.ts +32 -3
- package/src/plugins/builtin/power/gas-client.ts +110 -0
- package/src/plugins/builtin/power/gas-model.ts +194 -0
- package/src/plugins/builtin/power/gas.tsx +145 -0
- package/src/plugins/builtin/power/headless.ts +4 -1
- package/src/plugins/builtin/power/index.tsx +19 -9
- package/src/plugins/builtin/power/pane.tsx +2 -3
- package/src/plugins/builtin/power/prices-headless.ts +75 -7
- package/src/plugins/builtin/power/prices.tsx +36 -13
- package/src/plugins/builtin/primary-dealers/client.ts +108 -0
- package/src/plugins/builtin/primary-dealers/headless.ts +53 -0
- package/src/plugins/builtin/primary-dealers/index.tsx +17 -0
- package/src/plugins/builtin/primary-dealers/model.ts +206 -0
- package/src/plugins/builtin/primary-dealers/pane.tsx +149 -0
- package/src/plugins/builtin/relative-rotation/index.tsx +3 -0
- package/src/plugins/builtin/research/analyst-model.ts +9 -5
- package/src/plugins/builtin/research/headless-schema.ts +2 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +68 -32
- package/src/plugins/builtin/research/relative-valuation-model.ts +45 -24
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +39 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +8 -1
- package/src/plugins/builtin/sec/filing-display.tsx +3 -36
- package/src/plugins/builtin/sec/index.tsx +131 -43
- package/src/plugins/builtin/sec/model.ts +148 -2
- package/src/plugins/builtin/sec/row-summaries.ts +105 -0
- package/src/plugins/builtin/sectors/index.tsx +2 -0
- package/src/plugins/builtin/sectors/sector-model.ts +2 -2
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/locked-rows.tsx +1 -1
- package/src/plugins/builtin/shared/use-quote-board.ts +179 -43
- package/src/plugins/builtin/shared/wall-teaser.tsx +2 -2
- package/src/plugins/builtin/shared/zip-text.ts +46 -0
- package/src/plugins/builtin/short-interest/watch-model.ts +11 -2
- package/src/plugins/builtin/themes/model.ts +2 -2
- package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +11 -0
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +81 -19
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/headless.ts +2 -2
- package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +7 -5
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +8 -6
- package/src/plugins/builtin/time-sales/headless.ts +22 -0
- package/src/plugins/builtin/trace-bonds/client.ts +196 -0
- package/src/plugins/builtin/trace-bonds/headless.ts +69 -0
- package/src/plugins/builtin/trace-bonds/index.tsx +33 -0
- package/src/plugins/builtin/trace-bonds/model.ts +115 -0
- package/src/plugins/builtin/trace-bonds/pane.tsx +191 -0
- package/src/plugins/builtin/treasury-auctions/index.tsx +33 -47
- package/src/plugins/builtin/treasury-daily/client.ts +96 -0
- package/src/plugins/builtin/treasury-daily/headless.ts +69 -0
- package/src/plugins/builtin/treasury-daily/index.tsx +17 -0
- package/src/plugins/builtin/treasury-daily/model.ts +69 -0
- package/src/plugins/builtin/treasury-daily/pane.tsx +140 -0
- package/src/plugins/builtin/world-venue-map/entity-detail.tsx +15 -2
- package/src/plugins/builtin/world-venue-map/map.tsx +1 -0
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/catalog-browser.ts +25 -2
- package/src/plugins/marketplace/model.ts +57 -21
- package/src/renderers/dom/build-assets.ts +14 -5
- package/src/renderers/dom/data-table/index.tsx +5 -3
- package/src/renderers/dom/desktop/controls.tsx +2 -1
- package/src/renderers/dom/desktop/icons.tsx +25 -0
- package/src/renderers/dom/desktop/list-view.tsx +1 -1
- package/src/renderers/dom/desktop/menu.tsx +1 -1
- package/src/renderers/dom/desktop/query-bar.tsx +3 -1
- package/src/renderers/dom/host/chart-surface.tsx +7 -2
- package/src/renderers/dom/host/resize-rows.ts +111 -0
- package/src/renderers/dom/host/sticky-scroll.ts +22 -3
- package/src/renderers/dom/host/style.ts +14 -0
- package/src/renderers/dom/host/surface-3d.tsx +2 -1
- package/src/renderers/dom/host/surface-fit.ts +10 -6
- package/src/renderers/dom/host/text.tsx +2 -2
- package/src/renderers/dom/styles.css +63 -6
- package/src/renderers/electrobun/bun/desktop/app-badge.ts +37 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +8 -0
- package/src/renderers/electrobun/bun/desktop/macos-dock-badge.ts +37 -0
- package/src/renderers/electrobun/bun/desktop/macos-dock-icon.ts +2 -40
- package/src/renderers/electrobun/bun/desktop/macos-objc.ts +41 -0
- package/src/renderers/electrobun/bun/tui-entry.ts +18 -1
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/app-badge-host.ts +17 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +8 -3
- package/src/renderers/electrobun/view/main.tsx +2 -0
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +5 -1
- package/src/series/validate.ts +1 -1
- package/src/shares/api.ts +5 -1
- package/src/shares/location.ts +31 -0
- package/src/shares/open.ts +26 -0
- package/src/shares/pane-image.ts +18 -6
- package/src/shares/personal-pane.ts +42 -54
- package/src/sources/gloomberb-cloud/index.ts +7 -2
- package/src/sources/gloomberb-cloud/normalizers.ts +5 -0
- package/src/sources/history-coverage.ts +22 -4
- package/src/sources/history-result.ts +3 -1
- package/src/sources/provider-errors.ts +2 -1
- package/src/sources/provider-router/batches.ts +18 -2
- package/src/sources/provider-router/index.ts +2 -1
- package/src/state/app/context/index.tsx +3 -2
- package/src/test-support/builtin-panes.ts +31 -0
- package/src/tickers/listing-venues.ts +22 -0
- package/src/tickers/search/index.ts +7 -2
- package/src/tickers/search/ranking.ts +19 -0
- package/src/time-series/chart-data.ts +2 -0
- package/src/time-series/parsed-history-cache.ts +41 -11
- package/src/time-series/resolve.ts +47 -15
- package/src/types/config.ts +1 -1
- package/src/types/data-provider.ts +13 -1
- package/src/types/financials.ts +13 -0
- package/src/types/headless.ts +22 -0
- package/src/types/plugin.ts +33 -6
- package/src/types/price-history.ts +2 -0
- package/src/ui/host.tsx +6 -0
- package/src/utils/crypto-pair.ts +1 -1
- package/src/utils/currency-pair.ts +66 -0
- package/src/utils/decimal-comma.ts +57 -0
- package/src/utils/guards.ts +25 -0
- package/src/utils/local-utc-offset.ts +37 -0
- package/src/utils/plugin-proxy-hosts.json +8 -1
- package/src/components/chart/composite/crosshair-readout.tsx +0 -150
- package/src/plugins/builtin/chat/message/ai-badge.tsx +0 -18
- package/src/plugins/builtin/credit-boards/pane.tsx +0 -30
|
@@ -1,64 +1,52 @@
|
|
|
1
|
+
import type { ResolvedPane } from "../layout/pane-manager";
|
|
2
|
+
import { getPaneSettings } from "../pane-settings";
|
|
1
3
|
import type { PluginRegistry } from "../plugins/registry";
|
|
2
|
-
import
|
|
3
|
-
import type {
|
|
4
|
+
import { resolveCollectionForPane, resolveTickerForPane, type AppState } from "../state/app/context";
|
|
5
|
+
import type { LayoutConfig } from "../types/config";
|
|
6
|
+
import type { PanePersonalData, PanePersonalDataContext } from "../types/plugin";
|
|
7
|
+
import { resolvePanePersonalData } from "./pane-image";
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
9
|
+
export interface PanePersonalDataSource {
|
|
10
|
+
registry: Pick<PluginRegistry, "brokers" | "getPanePluginId" | "getBrokerPluginId">;
|
|
11
|
+
state: AppState;
|
|
12
|
+
/** The layout the pane is in, for its `personalData` rule. */
|
|
13
|
+
layout: LayoutConfig;
|
|
14
|
+
pane: Pick<ResolvedPane, "instance" | "def">;
|
|
15
|
+
}
|
|
7
16
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
/** What a pane shows now, for its `personalData` rule. */
|
|
18
|
+
function personalDataContext({ state, layout, pane }: Omit<PanePersonalDataSource, "registry">): PanePersonalDataContext {
|
|
19
|
+
const paneId = pane.instance.instanceId;
|
|
20
|
+
return {
|
|
21
|
+
config: state.config,
|
|
22
|
+
layout,
|
|
23
|
+
paneId,
|
|
24
|
+
paneType: pane.instance.paneId,
|
|
25
|
+
pane: pane.instance,
|
|
26
|
+
settings: getPaneSettings(pane.instance),
|
|
27
|
+
paneState: state.paneState[paneId] ?? {},
|
|
28
|
+
activeTicker: resolveTickerForPane(state, paneId),
|
|
29
|
+
activeCollectionId: resolveCollectionForPane(state, paneId),
|
|
30
|
+
tickers: state.tickers,
|
|
31
|
+
};
|
|
16
32
|
}
|
|
17
33
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (typeof value === "string") return [value];
|
|
23
|
-
return Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === "string") : [];
|
|
24
|
-
});
|
|
34
|
+
/** Whether the plugin that owns a pane also connects a broker. */
|
|
35
|
+
function ownerConnectsBroker(registry: PanePersonalDataSource["registry"], paneType: string): boolean {
|
|
36
|
+
const owner = registry.getPanePluginId(paneType);
|
|
37
|
+
return !!owner && [...registry.brokers.keys()].some((brokerType) => registry.getBrokerPluginId(brokerType) === owner);
|
|
25
38
|
}
|
|
26
39
|
|
|
27
40
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* - its plugin brings a broker, as an external broker plugin's panes do;
|
|
35
|
-
* - it shows a ticker the user holds (a research pane lists the position);
|
|
36
|
-
* - it is set to one of the user's portfolios or broker connections.
|
|
41
|
+
* The personal data a pane shows now, from its `PaneDef.personalData` (and,
|
|
42
|
+
* for a pane that declares nothing, the fallback in `resolvePanePersonalData`);
|
|
43
|
+
* null for market data. The one answer to "does this pane show the person's
|
|
44
|
+
* own data, so ask before it leaves the device": sharing its picture to chat
|
|
45
|
+
* and attaching it to an Ask Gloom question both read it here, from the same
|
|
46
|
+
* context.
|
|
37
47
|
*/
|
|
38
|
-
export function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
symbol,
|
|
43
|
-
config,
|
|
44
|
-
tickers,
|
|
45
|
-
}: PersonalPaneSource): boolean {
|
|
46
|
-
const def = registry.panes.get(instance.paneId);
|
|
47
|
-
if (def?.portableShare?.private) return true;
|
|
48
|
-
|
|
49
|
-
const pluginId = registry.getPanePluginId(instance.paneId);
|
|
50
|
-
if (pluginId && [...registry.brokers.keys()].some((brokerId) => registry.getBrokerPluginId(brokerId) === pluginId)) {
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const held = symbol ? tickers?.get(symbol)?.metadata.positions : undefined;
|
|
55
|
-
if (held?.some((position) => position.shares !== 0)) return true;
|
|
56
|
-
|
|
57
|
-
const accounts = new Set([
|
|
58
|
-
...config.portfolios.map((portfolio) => portfolio.id),
|
|
59
|
-
...config.brokerInstances.map((instance) => instance.id),
|
|
60
|
-
]);
|
|
61
|
-
return [instance.params, instance.settings, paneState]
|
|
62
|
-
.flatMap(accountValues)
|
|
63
|
-
.some((value) => accounts.has(value));
|
|
48
|
+
export function panePersonalData({ registry, ...source }: PanePersonalDataSource): PanePersonalData | null {
|
|
49
|
+
return resolvePanePersonalData(source.pane.def, personalDataContext(source), {
|
|
50
|
+
ownerConnectsBroker: ownerConnectsBroker(registry, source.pane.def.id),
|
|
51
|
+
});
|
|
64
52
|
}
|
|
@@ -2,7 +2,7 @@ import { exchangeRateMetadata } from "../../utils/exchange-rate-snapshot";
|
|
|
2
2
|
import type { ExchangeRateSnapshot } from "../../types/exchange-rate";
|
|
3
3
|
import type { TimeRange } from "../../time-series/range";
|
|
4
4
|
import { subtractTimeRange } from "../../time-series/date-window";
|
|
5
|
-
import { HistoryCoverageError, parseHistoryCoverageStart } from "../history-coverage";
|
|
5
|
+
import { HistoryCoverageError, parseHistoryCoverageNote, parseHistoryCoverageStart } from "../history-coverage";
|
|
6
6
|
import {
|
|
7
7
|
normalizeChartResolutionSupport,
|
|
8
8
|
type ChartResolutionSupport,
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
SecFilingDocument,
|
|
17
17
|
SecFilingItem,
|
|
18
18
|
MarketDataRequestContext,
|
|
19
|
+
QuoteBatchOptions,
|
|
19
20
|
QuoteBatchResult,
|
|
20
21
|
QuoteSubscriptionTarget,
|
|
21
22
|
SearchRequestContext,
|
|
@@ -177,6 +178,7 @@ function mapCloudPriceHistory(
|
|
|
177
178
|
extendedHours = false,
|
|
178
179
|
): PriceHistoryResult {
|
|
179
180
|
const coverageStart = parseHistoryCoverageStart(response.coverage);
|
|
181
|
+
const coverageNote = parseHistoryCoverageNote(response.coverage);
|
|
180
182
|
if (response.status === "empty" && coverageStart
|
|
181
183
|
&& requestedStart.getTime() < Date.parse(coverageStart)) throw new HistoryCoverageError(coverageStart);
|
|
182
184
|
// The server marks a cached copy it could not refresh in time. Bars that
|
|
@@ -237,6 +239,7 @@ function mapCloudPriceHistory(
|
|
|
237
239
|
resolution,
|
|
238
240
|
...(session && matchingSource && matchingIdentity ? { session } : {}),
|
|
239
241
|
...(coverageStart ? { coverageStart } : {}),
|
|
242
|
+
...(coverageNote ? { coverageNote } : {}),
|
|
240
243
|
...(extendedHours ? { extendedHours: true } : {}),
|
|
241
244
|
};
|
|
242
245
|
}
|
|
@@ -492,8 +495,10 @@ export class GloomberbCloudProvider implements AssetDataProvider {
|
|
|
492
495
|
|
|
493
496
|
async getQuotesBatch(
|
|
494
497
|
targets: QuoteSubscriptionTarget[],
|
|
495
|
-
options:
|
|
498
|
+
options: QuoteBatchOptions = {},
|
|
496
499
|
): Promise<QuoteBatchResult[]> {
|
|
500
|
+
// This provider keeps no quote cache of its own.
|
|
501
|
+
if (options.cacheOnly) return targets.map((target) => ({ target, quote: null }));
|
|
497
502
|
const { perpetuals, others } = splitPerpetualTargets(targets);
|
|
498
503
|
if (perpetuals.length) {
|
|
499
504
|
const [listed, answered] = await Promise.all([
|
|
@@ -120,6 +120,11 @@ export function mapQuote(
|
|
|
120
120
|
divisor,
|
|
121
121
|
),
|
|
122
122
|
extendedSessionPrint: typeof quote.extendedSessionPrint === "boolean" ? quote.extendedSessionPrint : undefined,
|
|
123
|
+
overnightPrice: normalizePriceValueByDivisor(quote.overnightPrice, divisor),
|
|
124
|
+
overnightChange: normalizePriceValueByDivisor(quote.overnightChange, divisor),
|
|
125
|
+
overnightPriceKind: quote.overnightPriceKind === "trade" || quote.overnightPriceKind === "mark"
|
|
126
|
+
? quote.overnightPriceKind
|
|
127
|
+
: undefined,
|
|
123
128
|
listingExchangeName,
|
|
124
129
|
listingExchangeFullName,
|
|
125
130
|
exchangeName: listingExchangeName,
|
|
@@ -19,14 +19,32 @@ export function parseHistoryCoverageStart(coverage: unknown): string | undefined
|
|
|
19
19
|
return dates.length ? dates.reduce((latest, date) => date > latest ? date : latest) : undefined;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Why a source serves nothing before its coverage, when it says, such as
|
|
24
|
+
* "old-shekel quotes before 1992 are not served": one line of plain text.
|
|
25
|
+
*/
|
|
26
|
+
export function parseHistoryCoverageNote(coverage: unknown): string | undefined {
|
|
27
|
+
if (!coverage || typeof coverage !== "object" || !parseHistoryCoverageStart(coverage)) return undefined;
|
|
28
|
+
return historyCoverageNoteText((coverage as Record<string, unknown>).note);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A coverage note fit to print: a short line without control characters, its closing full stop dropped. */
|
|
32
|
+
export function historyCoverageNoteText(value: unknown): string | undefined {
|
|
33
|
+
if (typeof value !== "string") return undefined;
|
|
34
|
+
const note = value.trim().replace(/\.$/, "");
|
|
35
|
+
return note && note.length <= 200 && !/[\u0000-\u001f\u007f]/.test(note) ? note : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function historyCoverageMessage(coverageStart: string, note?: string): string {
|
|
39
|
+
return note
|
|
40
|
+
? `Price history starts ${coverageStart}; ${note}.`
|
|
41
|
+
: `Verified price history starts ${coverageStart}; earlier prices are excluded.`;
|
|
24
42
|
}
|
|
25
43
|
|
|
26
44
|
/** Shown while the visible window starts before the source's coverage. */
|
|
27
|
-
export function historyCoverageNotice(coverageStart: string | undefined, visibleStart: number | null): string | null {
|
|
45
|
+
export function historyCoverageNotice(coverageStart: string | undefined, visibleStart: number | null, note?: string): string | null {
|
|
28
46
|
if (!coverageStart || (visibleStart !== null && visibleStart >= Date.parse(coverageStart))) return null;
|
|
29
|
-
return historyCoverageMessage(coverageStart);
|
|
47
|
+
return historyCoverageMessage(coverageStart, note);
|
|
30
48
|
}
|
|
31
49
|
|
|
32
50
|
/** The whole requested window lies before the source's declared coverage. */
|
|
@@ -4,7 +4,7 @@ import type { DataProvider, MarketDataRequestContext } from "../types/data-provi
|
|
|
4
4
|
import type { PriceHistoryResult } from "../types/price-history";
|
|
5
5
|
import { parseHistorySession } from "../market-data/history-session";
|
|
6
6
|
import { canonicalHistoryInterval } from "./history-retention";
|
|
7
|
-
import { isHistoryCoverageDate } from "./history-coverage";
|
|
7
|
+
import { historyCoverageNoteText, isHistoryCoverageDate } from "./history-coverage";
|
|
8
8
|
|
|
9
9
|
export type HistoryResultRequest =
|
|
10
10
|
| { kind: "range"; range: TimeRange }
|
|
@@ -40,6 +40,8 @@ export function normalizeHistoryResult(
|
|
|
40
40
|
...(session ? { session } : {}),
|
|
41
41
|
...(typeof result.sourceKey === "string" && /^(provider|broker):[^\r\n\0]+$/.test(result.sourceKey) ? { sourceKey: result.sourceKey } : {}),
|
|
42
42
|
...(isHistoryCoverageDate(result.coverageStart) ? { coverageStart: result.coverageStart } : {}),
|
|
43
|
+
...(isHistoryCoverageDate(result.coverageStart) && historyCoverageNoteText(result.coverageNote)
|
|
44
|
+
? { coverageNote: historyCoverageNoteText(result.coverageNote) } : {}),
|
|
43
45
|
...(result.extendedHours === true ? { extendedHours: true } : {}),
|
|
44
46
|
...(result.savedCopyBehind === true ? { savedCopyBehind: true as const } : {}),
|
|
45
47
|
};
|
|
@@ -71,7 +71,8 @@ export function noteProviderAnswer(note: ProviderMissNote | undefined): void {
|
|
|
71
71
|
if (note) note.notFound = false;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
// A command may follow it with the listing search suggests: "Not a ticker: USD_NGN. Did you mean USDNGN=X (...)?"
|
|
75
|
+
const NOT_A_TICKER = /^Not a ticker: (.+?)\.(?= Did you mean |$)/;
|
|
75
76
|
|
|
76
77
|
/** "Not a ticker: APPLE.", what a request for a symbol no listing carries ends in. */
|
|
77
78
|
function notATickerMessage(symbol: string): string {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
CachedFinancialsTarget,
|
|
3
3
|
MarketDataRequestContext,
|
|
4
|
+
QuoteBatchOptions,
|
|
4
5
|
QuoteBatchResult,
|
|
5
6
|
QuoteSubscriptionTarget,
|
|
6
7
|
TickerFinancialsBatchResult,
|
|
@@ -48,8 +49,11 @@ export class ProviderRouterBatchRoutes {
|
|
|
48
49
|
|
|
49
50
|
async getQuotesBatch(
|
|
50
51
|
targets: QuoteSubscriptionTarget[],
|
|
51
|
-
options:
|
|
52
|
+
options: QuoteBatchOptions = {},
|
|
52
53
|
): Promise<QuoteBatchResult[]> {
|
|
54
|
+
if (options.cacheOnly) return targets.map((target) => ({ target, quote: this.cachedQuote(target) }));
|
|
55
|
+
// The signal is the caller's; providers are asked as before.
|
|
56
|
+
const providerOptions = { forceRefresh: options.forceRefresh };
|
|
53
57
|
const forceRefresh = options.forceRefresh === true;
|
|
54
58
|
const results = new Array<QuoteBatchResult | null>(targets.length).fill(null);
|
|
55
59
|
const misses: Array<{ index: number; target: QuoteSubscriptionTarget }> = [];
|
|
@@ -91,7 +95,7 @@ export class ProviderRouterBatchRoutes {
|
|
|
91
95
|
providerIndexes.set(key, bucket);
|
|
92
96
|
}
|
|
93
97
|
const uniqueTargets = [...providerIndexes.values()].map((bucket) => bucket[0]!.target);
|
|
94
|
-
const batchResults = await batchProvider.getQuotesBatch!(uniqueTargets,
|
|
98
|
+
const batchResults = await batchProvider.getQuotesBatch!(uniqueTargets, providerOptions).catch(() => []);
|
|
95
99
|
for (const item of batchResults) {
|
|
96
100
|
if (!isProviderQuoteUsableForCurrentSession(item.quote, item.target.exchange, item.target.symbol, { recentAnswer: true })) continue;
|
|
97
101
|
const key = this.quoteBatchKey(item.target);
|
|
@@ -197,6 +201,18 @@ export class ProviderRouterBatchRoutes {
|
|
|
197
201
|
return results.map((result, index) => result ?? { target: targets[index]!, financials: null });
|
|
198
202
|
}
|
|
199
203
|
|
|
204
|
+
/** The newest cached quote for a target from any source, however old: what a board paints first. */
|
|
205
|
+
private cachedQuote(target: QuoteSubscriptionTarget): Quote | null {
|
|
206
|
+
const context = target.context;
|
|
207
|
+
const entityKey = this.deps.getEntityKey(target.symbol, context?.instrument);
|
|
208
|
+
const variantKeys = this.deps.getTickerVariantCandidates(target.exchange);
|
|
209
|
+
const sourceKeys = [
|
|
210
|
+
...this.deps.getBrokerCandidatesForContext(context, false).map((candidate) => this.deps.brokerSourceKey(candidate)),
|
|
211
|
+
...this.deps.getProviderSourceKeys(),
|
|
212
|
+
];
|
|
213
|
+
return selectCachedResource<Quote>(this.deps.resources, "quote", entityKey, variantKeys, sourceKeys, true)?.value ?? null;
|
|
214
|
+
}
|
|
215
|
+
|
|
200
216
|
private quoteBatchKey(target: QuoteSubscriptionTarget): string {
|
|
201
217
|
return `${target.symbol.trim().toUpperCase()}:${canonicalExchange(target.exchange ?? "")}`;
|
|
202
218
|
}
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
CachedFinancialsTarget,
|
|
12
12
|
DataProvider,
|
|
13
13
|
MarketDataRequestContext,
|
|
14
|
+
QuoteBatchOptions,
|
|
14
15
|
QuoteBatchResult,
|
|
15
16
|
QuoteSubscriptionTarget,
|
|
16
17
|
SearchRequestContext,
|
|
@@ -194,7 +195,7 @@ export class AssetDataRouter implements DataProvider {
|
|
|
194
195
|
|
|
195
196
|
async getQuotesBatch(
|
|
196
197
|
targets: QuoteSubscriptionTarget[],
|
|
197
|
-
options:
|
|
198
|
+
options: QuoteBatchOptions = {},
|
|
198
199
|
): Promise<QuoteBatchResult[]> {
|
|
199
200
|
const mapped = mapListingTargets(targets, (target) => this.resolvePublicExchange(target.symbol, target.exchange, target.context));
|
|
200
201
|
const results = await this.batchRoutes.getQuotesBatch(mapped.valid, options);
|
|
@@ -398,13 +398,14 @@ function useUpdatePaneLayout(options: { saveDelayMs?: number } = {}) {
|
|
|
398
398
|
}, [dispatch, saveDelayMs, stateRef]);
|
|
399
399
|
}
|
|
400
400
|
|
|
401
|
-
/** Keep a content-derived pane title in its saved layout, without exposing config writes. */
|
|
402
|
-
export function usePaneTitle(title: string, paneId?: string): void {
|
|
401
|
+
/** Keep a content-derived pane title in its saved layout, without exposing config writes. Null leaves the title alone. */
|
|
402
|
+
export function usePaneTitle(title: string | null, paneId?: string): void {
|
|
403
403
|
const id = useScopedPaneId(paneId);
|
|
404
404
|
// A follower's title changes with every cursor move in its source; that is
|
|
405
405
|
// not worth a config write per step.
|
|
406
406
|
const updateLayout = useUpdatePaneLayout({ saveDelayMs: LOW_PRIORITY_CONFIG_SAVE_DEBOUNCE_MS });
|
|
407
407
|
useEffect(() => {
|
|
408
|
+
if (title == null) return;
|
|
408
409
|
updateLayout((layout) => {
|
|
409
410
|
const pane = findPaneInstance(layout, id);
|
|
410
411
|
return !pane || pane.title === title ? layout
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createPaneDiscoveryContext } from "../cli/pane-functions/discovery";
|
|
2
|
+
import { getLoadablePlugins } from "../plugins/catalog";
|
|
3
|
+
import { createDefaultConfig } from "../types/config";
|
|
4
|
+
import type { GloomPlugin, PaneDef } from "../types/plugin";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Every built-in pane definition, the panes a plugin registers in `setup()`
|
|
8
|
+
* included, as `gloomberb catalog` finds them. `dispose` stops the plugins
|
|
9
|
+
* that were set up.
|
|
10
|
+
*/
|
|
11
|
+
export async function loadBuiltinPanes(): Promise<{ panes: Map<string, PaneDef>; dispose: () => void }> {
|
|
12
|
+
const config = createDefaultConfig("/gloom");
|
|
13
|
+
const { panes, paneTemplates: _templates, ...discovery } = createPaneDiscoveryContext({
|
|
14
|
+
getConfig: () => config,
|
|
15
|
+
panes: new Map(getLoadablePlugins().flatMap((plugin) => plugin.panes ?? []).map((pane) => [pane.id, pane])),
|
|
16
|
+
});
|
|
17
|
+
const started: GloomPlugin[] = [];
|
|
18
|
+
const dispose = () => {
|
|
19
|
+
for (const plugin of started.splice(0).reverse()) plugin.dispose?.();
|
|
20
|
+
};
|
|
21
|
+
try {
|
|
22
|
+
for (const plugin of getLoadablePlugins()) {
|
|
23
|
+
started.push(plugin);
|
|
24
|
+
await plugin.setup?.(discovery);
|
|
25
|
+
}
|
|
26
|
+
} catch (error) {
|
|
27
|
+
dispose();
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
return { panes, dispose };
|
|
31
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
parsePublicTickerKey,
|
|
11
11
|
typedExchangeCode,
|
|
12
12
|
} from "../utils/exchanges";
|
|
13
|
+
import { currencyPairSpelling } from "../utils/currency-pair";
|
|
13
14
|
|
|
14
15
|
export interface ListingStore {
|
|
15
16
|
loadTicker(symbol: string): Promise<TickerRecord | null>;
|
|
@@ -78,6 +79,27 @@ export async function quotedOnVenue(listing: { symbol: string; exchange: string
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
/**
|
|
83
|
+
* A currency pair typed the way a security could also be written, USD-NGN or
|
|
84
|
+
* USDNGN, read as the pair the service lists (USDNGN=X) when search, as the
|
|
85
|
+
* command bar reads the same text, finds no listing under those letters: MNT-USD
|
|
86
|
+
* stays the coin. Null for any other text, and for a listing.
|
|
87
|
+
*/
|
|
88
|
+
export async function readAmbiguousCurrencyPair(typed: string, { store, dataProvider }: ListingDeps): Promise<string | null> {
|
|
89
|
+
const pair = currencyPairSpelling(typed);
|
|
90
|
+
if (!pair?.ambiguous) return null;
|
|
91
|
+
try {
|
|
92
|
+
const tickers = new Map((await store.loadAllTickers()).map((ticker) => [ticker.metadata.ticker.toUpperCase(), ticker] as const));
|
|
93
|
+
const resolved = await resolveTickerSearch({ query: typed, activeTicker: null, tickers, dataProvider });
|
|
94
|
+
const exchange = !resolved ? undefined : resolved.kind === "local" ? resolved.ticker.metadata.exchange : resolved.result.exchange;
|
|
95
|
+
const listed = resolved ? currencyPairSpelling(resolved.symbol, exchange) : null;
|
|
96
|
+
return listed && !listed.ambiguous && listed.symbol === pair.symbol ? pair.symbol : null;
|
|
97
|
+
} catch {
|
|
98
|
+
// Several listings under those letters, or no search: the letters stay a symbol.
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
81
103
|
/** What a typed code that names several venues (7203:TSE) was read as. */
|
|
82
104
|
export type SharedCodeReading =
|
|
83
105
|
/** The one of the code's venues the symbol trades on. */
|
|
@@ -5,6 +5,7 @@ import { canonicalExchange, isSharedExchangeCode, isUsListingExchange, parsePubl
|
|
|
5
5
|
import { getListingSymbol, tickerHasListingSuffix } from "../../sources/listing-symbols";
|
|
6
6
|
import { parseOptionSymbol } from "../../utils/options";
|
|
7
7
|
import { resolveCurrencyUnit } from "../../utils/currency-units";
|
|
8
|
+
import { currencyPairSpelling } from "../../utils/currency-pair";
|
|
8
9
|
import { assetClassMarketSymbol, parseAssetClassQuery } from "./asset-classes";
|
|
9
10
|
import { bareFuturesCode, futuresContractByCode } from "../../market-data/futures-board";
|
|
10
11
|
import { searchContractKey, searchInstrumentKey } from "./identity";
|
|
@@ -311,9 +312,13 @@ export function resolveTickerSearch(options: ResolveTickerSearchOptions): Promis
|
|
|
311
312
|
const typed = symbolSearchQuery(requested);
|
|
312
313
|
// A futures board code no security uses is its future (TTF is TTF=F), unless a saved ticker holds it.
|
|
313
314
|
const future = pickListing || options.tickers.has(typed) ? null : bareFuturesCode(typed);
|
|
314
|
-
|
|
315
|
+
// A currency pair is the one listing the service has for it, however it is written: EUR/USD and
|
|
316
|
+
// JPY=X are EURUSD=X and USDJPY=X. USD-NGN and USDNGN, which a security could also be written as
|
|
317
|
+
// (MNT-USD is a coin), are the pair only when nothing is listed under those letters.
|
|
318
|
+
const pair = pickListing || future ? null : currencyPairSpelling(typed);
|
|
319
|
+
const symbol = future?.symbol ?? (pair && !pair.ambiguous ? pair.symbol : typed);
|
|
315
320
|
const literal = resolveTickerSymbol(symbol, options, pickListing ? requested : null);
|
|
316
|
-
const alternatives = pickListing ? [] : dottedVenueQueries(symbol);
|
|
321
|
+
const alternatives = pickListing ? [] : [...dottedVenueQueries(symbol), ...pair?.ambiguous ? [pair.symbol] : []];
|
|
317
322
|
// Most input has no venue code to retry: hand back the literal lookup
|
|
318
323
|
// itself, so it settles no later than it did before the retry existed.
|
|
319
324
|
if (alternatives.length === 0) return literal;
|
|
@@ -649,6 +649,23 @@ function scoreSearchAlias(intent: SearchQueryIntent, alias: string): number {
|
|
|
649
649
|
return score;
|
|
650
650
|
}
|
|
651
651
|
|
|
652
|
+
/**
|
|
653
|
+
* Whether the query is, letter for letter, one of the codes the server sent an
|
|
654
|
+
* index with: IBOV and "IBOV Index" for ^BVSP. A code is how that index is
|
|
655
|
+
* typed (the symbol itself carries a caret nobody types), so the index answers
|
|
656
|
+
* to it as to an exact symbol. Only an index does: a stock or a fund named by a
|
|
657
|
+
* code keeps the rank its own symbol gives it.
|
|
658
|
+
*/
|
|
659
|
+
export function answersToIndexCode(
|
|
660
|
+
item: Pick<TickerSearchRankableItem, "searchAliases" | "instrumentType">,
|
|
661
|
+
query: string,
|
|
662
|
+
): boolean {
|
|
663
|
+
const code = (text: string) => text.trim().replace(/\s+/g, " ").toUpperCase();
|
|
664
|
+
const typed = code(query);
|
|
665
|
+
return !!typed && normalizeSearchText(item.instrumentType ?? "") === "INDEX"
|
|
666
|
+
&& !!item.searchAliases?.some((alias) => code(alias) === typed);
|
|
667
|
+
}
|
|
668
|
+
|
|
652
669
|
function scoreSymbolMatchRank(
|
|
653
670
|
intent: SearchQueryIntent,
|
|
654
671
|
item: Pick<TickerSearchRankableItem, "label"> & Partial<TickerSearchRankableItem>,
|
|
@@ -656,6 +673,8 @@ function scoreSymbolMatchRank(
|
|
|
656
673
|
if (normalizeTickerSymbol(item.symbol || item.label) === normalizeTickerSymbol(intent.rawQuery)) return 4;
|
|
657
674
|
if (isQualifiedTickerQuery(intent.rawQuery)) return matchesQualifiedTicker(item, intent.rawQuery) ? 4 : 0;
|
|
658
675
|
if (parsePublicTickerKey(item.symbol || item.label).symbol === normalizeTickerSymbol(intent.rawQuery)) return 4;
|
|
676
|
+
// Below a real symbol of that name (SPX Technologies), above every partial match (IBOV11).
|
|
677
|
+
if (answersToIndexCode(item, intent.rawQuery)) return 2;
|
|
659
678
|
if (isCryptoInstrumentType(item.instrumentType)) return 0;
|
|
660
679
|
if (isExplicitMarketSymbol(item.symbol || item.label)) return 0;
|
|
661
680
|
if (!intent.normalizedQuery && !intent.compactQuery) return 0;
|
|
@@ -139,6 +139,8 @@ export function liveQuoteObservation(
|
|
|
139
139
|
): LiveQuoteObservation | null {
|
|
140
140
|
if (!quote || isQuoteStaleForCurrentSession(quote, quoteObservationNow(quote, now))) return null;
|
|
141
141
|
if (hasUnknownBondHistoryBasis(quote, assetCategory)) return null;
|
|
142
|
+
// No history covers the overnight session: its prices form or move no bar.
|
|
143
|
+
if (quote.overnightPrice != null) return null;
|
|
142
144
|
const quoteTime = quote.lastUpdated;
|
|
143
145
|
const quotePrice = getActiveQuotePrice(quote);
|
|
144
146
|
if (
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import type { PricePoint } from "../types/financials";
|
|
1
|
+
import type { PricePoint, QuoteMetadata } from "../types/financials";
|
|
2
2
|
import type { PriceHistoryResult } from "../types/price-history";
|
|
3
3
|
import type { InstrumentRef } from "../market-data/request-types";
|
|
4
4
|
import { instrumentIdentityKey } from "../utils/instrument-identity";
|
|
5
5
|
import type { TimeRange } from "./range";
|
|
6
|
-
import type { ManualChartResolution } from "./resolution";
|
|
6
|
+
import type { ChartResolutionSupport, ManualChartResolution } from "./resolution";
|
|
7
7
|
|
|
8
8
|
const MAX_PARSED_HISTORY = 32;
|
|
9
9
|
const parsedHistory = new Map<string, PriceHistoryResult>();
|
|
10
10
|
|
|
11
|
+
function listingKey(instrument: InstrumentRef): string {
|
|
12
|
+
return instrumentIdentityKey({ ...instrument,
|
|
13
|
+
brokerId: instrument.brokerId ?? instrument.instrument?.brokerId,
|
|
14
|
+
brokerInstanceId: instrument.brokerInstanceId ?? instrument.instrument?.brokerInstanceId,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
export function parsedPriceHistoryKey(
|
|
12
19
|
instrument: InstrumentRef,
|
|
13
20
|
range: TimeRange,
|
|
@@ -15,15 +22,38 @@ export function parsedPriceHistoryKey(
|
|
|
15
22
|
): string {
|
|
16
23
|
// This is an in-memory seed cache. Never reuse earlier symbol-only keys:
|
|
17
24
|
// their observations may have come from a different broker contract.
|
|
18
|
-
return JSON.stringify([
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
return JSON.stringify(["parsed-history-v2", listingKey(instrument), range, resolution ?? ""]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* What a settled chart learned about a listing that its cached bars do not
|
|
30
|
+
* carry: the quote's currency and instrument type, and the intervals its
|
|
31
|
+
* source serves. A seed drawn without them labels its axes without units and
|
|
32
|
+
* offers placeholder intervals, so the settled chart that replaces it a
|
|
33
|
+
* moment later moves the plot and redraws every label.
|
|
34
|
+
*/
|
|
35
|
+
interface SeedListingFacts {
|
|
36
|
+
quoteMetadata?: QuoteMetadata;
|
|
37
|
+
resolutionSupport?: ChartResolutionSupport[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const MAX_LISTING_FACTS = 64;
|
|
41
|
+
const listingFacts = new Map<string, SeedListingFacts>();
|
|
42
|
+
|
|
43
|
+
export function rememberSeedListingFacts(instrument: InstrumentRef, facts: SeedListingFacts): void {
|
|
44
|
+
const key = listingKey(instrument);
|
|
45
|
+
const merged = { ...listingFacts.get(key), ...facts };
|
|
46
|
+
listingFacts.delete(key);
|
|
47
|
+
listingFacts.set(key, merged);
|
|
48
|
+
while (listingFacts.size > MAX_LISTING_FACTS) {
|
|
49
|
+
const oldest = listingFacts.keys().next().value;
|
|
50
|
+
if (oldest === undefined) break;
|
|
51
|
+
listingFacts.delete(oldest);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function readSeedListingFacts(instrument: InstrumentRef): SeedListingFacts | undefined {
|
|
56
|
+
return listingFacts.get(listingKey(instrument));
|
|
27
57
|
}
|
|
28
58
|
|
|
29
59
|
export function rememberParsedPriceHistory(
|