gloomberb 0.16.4 → 0.16.5
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/README.md +14 -91
- package/package.json +4 -1
- package/src/{plugins/builtin/cloud → account}/auth-form.tsx +9 -9
- package/src/{plugins/builtin/cloud → account}/auth-model.ts +4 -4
- package/src/account/billing.ts +57 -0
- package/src/{plugins/builtin/cloud → account}/device-signin-dialog.tsx +18 -12
- package/src/{plugins/builtin/cloud → account}/device-signin.ts +2 -2
- package/src/{plugins/builtin/cloud → account}/email-auth-attempt.ts +2 -2
- package/src/account/session.ts +16 -0
- package/src/{plugins/builtin/cloud → account}/upgrade-dialog.tsx +16 -15
- package/src/{plugins/builtin/cloud → account}/upgrade-personalization.ts +7 -7
- package/src/{plugins/builtin/cloud → account}/upgrade-tickers.ts +6 -6
- package/src/{plugins/builtin/cloud → account}/verification-panel.tsx +6 -6
- package/src/api-client/data.ts +24 -2
- package/src/api-client/earnings.ts +6 -0
- package/src/api-client/futures-curve.ts +56 -0
- package/src/api-client/index.ts +4 -1
- package/src/api-client/market-discovery.ts +10 -1
- package/src/api-client/paths.ts +7 -0
- package/src/api-client/research-activity.ts +15 -10
- package/src/api-client/types.ts +17 -1
- package/src/api-client/yield-curves.ts +8 -0
- package/src/app/global-shortcuts.ts +15 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +7 -3
- package/src/architecture/core-builtin-allowlist.json +4 -13
- package/src/brokers/signed-in/sign-in-dialog.tsx +11 -5
- package/src/capabilities/chart-series.ts +11 -1
- package/src/cli/commands/crypto-hints.ts +7 -0
- package/src/cli/commands/fred-series.ts +61 -0
- package/src/cli/commands/market.ts +407 -104
- package/src/cli/commands/news-hints.ts +62 -0
- package/src/cli/commands/news-search.ts +53 -0
- package/src/cli/commands/options-leaps.ts +5 -2
- package/src/cli/commands/overview.ts +93 -20
- package/src/cli/commands/remote.ts +2 -1
- package/src/cli/commands/search.ts +82 -9
- package/src/cli/commands/system.ts +55 -8
- package/src/cli/commands/ticker.ts +88 -11
- package/src/cli/context.ts +45 -14
- package/src/cli/desktop-pane-shot.ts +59 -7
- package/src/cli/earnings-calendar.ts +71 -0
- package/src/cli/freshness.ts +18 -7
- package/src/cli/glossary.ts +429 -0
- package/src/cli/history-rows.ts +48 -7
- package/src/cli/index-roots.ts +7 -0
- package/src/cli/index.ts +21 -7
- package/src/cli/known-gaps.ts +27 -0
- package/src/cli/listing-arg.ts +417 -56
- package/src/cli/not-a-ticker.ts +12 -5
- package/src/cli/pane-functions/access-gate.ts +5 -0
- package/src/cli/pane-functions/capabilities.ts +3 -2
- package/src/cli/pane-functions/catalog.ts +35 -5
- package/src/cli/pane-functions/data.ts +12 -8
- package/src/cli/pane-functions/discovery.ts +1 -0
- package/src/cli/pane-functions/dom.ts +20 -6
- package/src/cli/pane-functions/freshness.ts +363 -43
- package/src/cli/pane-functions/function-help.ts +12 -5
- package/src/cli/pane-functions/headless.ts +45 -17
- package/src/cli/pane-functions/index.ts +63 -10
- package/src/cli/pane-functions/market-session.ts +22 -8
- package/src/cli/pane-functions/options.ts +63 -22
- package/src/cli/pane-functions/report-notices.ts +25 -7
- package/src/cli/pane-functions/report.ts +15 -8
- package/src/cli/pane-functions/resolver.ts +49 -12
- package/src/cli/pane-functions/screenshot.ts +110 -23
- package/src/cli/report-tables.ts +3 -0
- package/src/cli/result.ts +4 -1
- package/src/cli/staged-tickers.ts +52 -0
- package/src/cli/symbol-args.ts +9 -0
- package/src/cli/symbol-list.ts +172 -0
- package/src/cli/ticker-resolution.ts +223 -30
- package/src/cli/types.ts +5 -0
- package/src/components/chart/composite/axis-overlays.tsx +6 -2
- package/src/components/chart/composite/composite-chart.tsx +40 -6
- package/src/components/chart/composite/legend.tsx +49 -2
- package/src/components/chart/composite/panel-surface.tsx +44 -17
- package/src/components/chart/composite/price-axis-labels.tsx +3 -1
- package/src/components/command-bar/panel/index.tsx +11 -3
- package/src/components/command-bar/panel/runtime.ts +3 -0
- package/src/components/command-bar/panel/types.ts +2 -0
- package/src/components/command-bar/routes/root/provider-search.ts +4 -3
- package/src/components/command-bar/routes/ticker-search/results.ts +12 -11
- package/src/components/command-bar/routes/ticker-search/route.ts +4 -3
- package/src/components/command-bar/workflow/tickers.ts +23 -4
- package/src/components/layout/detached-pane-shell.tsx +9 -0
- package/src/components/layout/floating-pane.tsx +3 -0
- package/src/components/layout/header.tsx +20 -5
- package/src/components/layout/pane/content.tsx +7 -13
- package/src/components/layout/pane/footer/index.tsx +6 -2
- package/src/components/layout/pane/footer/model.ts +35 -1
- package/src/components/layout/pane/header.tsx +24 -1
- package/src/components/layout/pane/index.tsx +12 -2
- package/src/components/layout/pane/sidebar.tsx +78 -10
- package/src/components/layout/shell/active-drag.ts +21 -5
- package/src/components/layout/shell/drag/index.ts +39 -9
- package/src/components/layout/shell/drag/live.ts +61 -51
- package/src/components/layout/shell/drag/runtime.ts +54 -12
- package/src/components/layout/shell/index.tsx +25 -7
- package/src/components/layout/shell/menu.ts +5 -0
- package/src/components/layout/shell/native/pointer-runtime.ts +3 -1
- package/src/components/layout/shell/pane/layers.tsx +192 -45
- package/src/components/layout/shell/terminal-pointer-runtime.ts +24 -0
- package/src/components/markdown-text.tsx +62 -20
- package/src/components/metric-treemap/heat-surface.tsx +10 -1
- package/src/components/onboarding/account-step/form-panel.tsx +1 -1
- package/src/components/onboarding/account-step/index.tsx +1 -1
- package/src/components/onboarding/account-step/qr-panel.tsx +1 -1
- package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
- package/src/components/onboarding/onboarding-keyboard.ts +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +2 -2
- package/src/components/onboarding/use-onboarding-upgrade.ts +3 -3
- package/src/components/onboarding/wizard-account.ts +2 -2
- package/src/components/pane-template-wizard.tsx +9 -1
- package/src/components/price-selector-dialog.tsx +11 -2
- package/src/components/sign-in-gate.tsx +3 -3
- package/src/components/ticker/list-table-view.tsx +24 -1
- package/src/components/ui/action-row.tsx +2 -1
- package/src/components/ui/button.tsx +7 -1
- package/src/components/ui/choice-dialog.tsx +6 -4
- package/src/components/ui/confirm-dialog.tsx +28 -18
- package/src/components/ui/data-table/index.tsx +17 -6
- package/src/components/ui/data-table/opentui/index.tsx +37 -6
- package/src/components/ui/data-table/types.ts +12 -0
- package/src/components/ui/dialog-actions.tsx +64 -0
- package/src/components/ui/display.tsx +17 -3
- package/src/components/ui/frame.tsx +35 -2
- package/src/components/ui/icon.tsx +2 -0
- package/src/components/ui/text-prompt-dialog.tsx +11 -9
- package/src/data/cached-query.ts +2 -0
- package/src/data/config/store/normalize.ts +7 -2
- package/src/data/ticker-repository.ts +5 -0
- package/src/data/ticker-store.ts +15 -0
- package/src/docs/content.ts +215 -0
- package/src/docs/load.ts +139 -0
- package/src/docs/requests.ts +39 -0
- package/src/docs/snapshot.chunk.ts +8 -0
- package/src/docs/snapshot.json +7416 -0
- package/src/docs/topics.ts +216 -0
- package/src/i18n/es.ts +61 -4
- package/src/i18n/ja.ts +61 -4
- package/src/i18n/ko.ts +61 -4
- package/src/i18n/zh-cn.ts +61 -4
- package/src/i18n/zh-tw.ts +61 -4
- package/src/layout/desks.ts +1 -1
- package/src/layout/pane-manager/dock-tree.ts +18 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/chart.ts +10 -4
- package/src/market-data/coordinator/index.ts +6 -3
- package/src/{plugins/builtin/futures/contracts.ts → market-data/futures-board.ts} +81 -28
- package/src/market-data/futures-contract.ts +151 -0
- package/src/market-data/hooks.tsx +10 -2
- package/src/market-data/market/format.ts +39 -21
- package/src/market-data/market/freshness.ts +81 -18
- package/src/market-data/market/status.ts +19 -23
- package/src/market-data/published-apac-sessions.ts +23 -1
- package/src/market-data/quotes/contributions.ts +3 -0
- package/src/market-data/quotes/metadata.ts +7 -24
- package/src/market-data/quotes/resolution.ts +2 -0
- package/src/market-data/quotes/screener-live-quotes.ts +5 -4
- package/src/market-data/result-types.ts +2 -0
- package/src/plugins/builtin/account-management/model.ts +1 -56
- package/src/plugins/builtin/account-management/pane.tsx +6 -4
- package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
- package/src/plugins/builtin/alerts/cli.ts +313 -47
- package/src/plugins/builtin/alerts/events-pane.tsx +45 -7
- package/src/plugins/builtin/alerts/events.ts +23 -2
- package/src/plugins/builtin/alerts/history-pane.tsx +7 -2
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +41 -9
- package/src/plugins/builtin/alerts/pane.tsx +8 -0
- package/src/plugins/builtin/alerts/research-builder.ts +8 -3
- package/src/plugins/builtin/alerts/research-rules.ts +18 -2
- package/src/plugins/builtin/alerts/scheduled-check.ts +122 -0
- package/src/plugins/builtin/alerts/scheduled-sources.ts +25 -0
- package/src/plugins/builtin/alerts/scheduled.ts +254 -0
- package/src/plugins/builtin/alerts/targets.ts +92 -0
- package/src/plugins/builtin/analytics/index.tsx +4 -2
- package/src/plugins/builtin/analytics/risk-headless.ts +30 -12
- package/src/plugins/builtin/analytics/risk-math.ts +91 -27
- package/src/plugins/builtin/analytics/risk-model.ts +35 -5
- package/src/plugins/builtin/analytics/risk-pane.tsx +15 -8
- package/src/plugins/builtin/attention/index.tsx +2 -0
- package/src/plugins/builtin/attention/pane.tsx +3 -1
- package/src/plugins/builtin/attention/series.ts +26 -0
- package/src/plugins/builtin/awards/index.tsx +2 -0
- package/src/plugins/builtin/awards/pane.tsx +3 -1
- package/src/plugins/builtin/awards/series.ts +29 -0
- package/src/plugins/builtin/bond-calculator/model.ts +2 -1
- package/src/plugins/builtin/catalysts/pane.tsx +3 -1
- package/src/plugins/builtin/cds/client.ts +70 -13
- package/src/plugins/builtin/cds/headless.ts +116 -19
- package/src/plugins/builtin/cds/index.tsx +3 -1
- package/src/plugins/builtin/cds/model.ts +193 -31
- package/src/plugins/builtin/cds/pane.tsx +56 -14
- package/src/plugins/builtin/cds/series.ts +19 -0
- package/src/plugins/builtin/central-bank-rates/index.tsx +2 -0
- package/src/plugins/builtin/central-bank-rates/series.ts +42 -0
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +192 -255
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +84 -46
- package/src/plugins/builtin/chart-composer/editor.tsx +5 -4
- package/src/plugins/builtin/chart-composer/field-series.ts +40 -0
- package/src/plugins/builtin/chart-composer/headless.ts +8 -3
- package/src/plugins/builtin/chart-composer/index.tsx +9 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +58 -50
- package/src/plugins/builtin/chart-composer/series-expression.ts +18 -10
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +59 -3
- package/src/plugins/builtin/chart-composer/user-series.ts +84 -0
- package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +10 -2
- package/src/plugins/builtin/chat/controller/index.ts +2 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +111 -19
- package/src/plugins/builtin/cloud/askg/executor.ts +57 -3
- package/src/plugins/builtin/cloud/askg/manifest.ts +32 -7
- package/src/plugins/builtin/cloud/askg/model.ts +27 -8
- package/src/plugins/builtin/cloud/askg/protocol.ts +10 -1
- package/src/plugins/builtin/cloud/askg/tool-display.ts +3 -0
- package/src/plugins/builtin/cloud/askg/undo.ts +2 -0
- package/src/plugins/builtin/cloud/auth-actions.tsx +1 -1
- package/src/plugins/builtin/cloud/auth-commands.ts +1 -1
- package/src/plugins/builtin/cloud/auth-dialog.tsx +3 -3
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +3 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +77 -9
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +6 -1
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +9 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +35 -11
- package/src/plugins/builtin/cloud/thesis/exposure.ts +1 -1
- package/src/plugins/builtin/cloud/thesis/prompts.tsx +2 -1
- package/src/plugins/builtin/company-attention/app-rank.tsx +1 -1
- package/src/plugins/builtin/company-attention/headless.ts +19 -5
- package/src/plugins/builtin/company-attention/index.tsx +2 -0
- package/src/plugins/builtin/company-attention/model.ts +26 -7
- package/src/plugins/builtin/company-attention/pane.tsx +12 -9
- package/src/plugins/builtin/company-attention/series.ts +52 -0
- package/src/plugins/builtin/company-kpis/pane.tsx +4 -2
- package/src/plugins/builtin/composite-plugins.ts +19 -13
- package/src/plugins/builtin/cot/index.tsx +3 -1
- package/src/plugins/builtin/cot/model.ts +40 -11
- package/src/plugins/builtin/cot/series.ts +27 -0
- package/src/plugins/builtin/credit-boards/index.tsx +2 -0
- package/src/plugins/builtin/credit-boards/series.ts +66 -0
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -0
- package/src/plugins/builtin/credit-conditions/series.ts +26 -0
- package/src/plugins/builtin/credit-documents/headless.ts +31 -1
- package/src/plugins/builtin/credit-documents/pane.tsx +3 -1
- package/src/plugins/builtin/crypto-board/headless.ts +2 -3
- package/src/plugins/builtin/crypto-board/index.tsx +2 -0
- package/src/plugins/builtin/crypto-board/model.ts +12 -0
- package/src/plugins/builtin/crypto-board/pane.tsx +19 -12
- package/src/plugins/builtin/crypto-board/series.ts +48 -0
- package/src/plugins/builtin/custom-view/live-quotes.ts +2 -2
- package/src/plugins/builtin/debt-maturities/index.tsx +2 -0
- package/src/plugins/builtin/debt-maturities/series.ts +45 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +2 -2
- package/src/plugins/builtin/dividend-yield/index.tsx +2 -0
- package/src/plugins/builtin/dividend-yield/series.ts +20 -0
- package/src/plugins/builtin/docs/blocks.tsx +181 -0
- package/src/plugins/builtin/docs/index.tsx +68 -0
- package/src/plugins/builtin/docs/pane.tsx +426 -0
- package/src/plugins/builtin/docs/targets.ts +72 -0
- package/src/plugins/builtin/docs/use-docs.ts +60 -0
- package/src/plugins/builtin/doe/index.tsx +2 -0
- package/src/plugins/builtin/doe/series.ts +62 -0
- package/src/plugins/builtin/earnings/client.ts +3 -2
- package/src/plugins/builtin/earnings/format.ts +1 -5
- package/src/plugins/builtin/earnings/headless.ts +58 -6
- package/src/plugins/builtin/earnings/history-model.ts +15 -1
- package/src/plugins/builtin/earnings/history-table.ts +13 -2
- package/src/plugins/builtin/earnings/history.tsx +7 -3
- package/src/plugins/builtin/econ/fred-catalog.ts +189 -0
- package/src/plugins/builtin/econ/fred-series-map.ts +0 -46
- package/src/plugins/builtin/econ/index.tsx +2 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +5 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +19 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +1 -0
- package/src/plugins/builtin/equity-screener/pane.tsx +4 -11
- package/src/plugins/builtin/exposure/holdings.ts +1 -1
- package/src/plugins/builtin/exposure/pane.tsx +3 -1
- package/src/plugins/builtin/fear-greed/index.tsx +4 -0
- package/src/plugins/builtin/fear-greed/series.ts +52 -0
- package/src/plugins/builtin/futures/index.tsx +43 -3
- package/src/plugins/builtin/futures/model.ts +1 -1
- package/src/plugins/builtin/futures/returns.ts +1 -1
- package/src/plugins/builtin/futures/series.ts +13 -0
- package/src/plugins/builtin/futures/table.tsx +1 -1
- package/src/plugins/builtin/futures/use-front-returns.ts +1 -1
- package/src/plugins/builtin/futures-curve/client.ts +12 -3
- package/src/plugins/builtin/futures-curve/headless.ts +70 -9
- package/src/plugins/builtin/futures-curve/index.tsx +1 -1
- package/src/plugins/builtin/futures-curve/model.ts +66 -11
- package/src/plugins/builtin/futures-curve/pane.tsx +52 -18
- package/src/plugins/builtin/fx-matrix/index.tsx +1 -1
- package/src/plugins/builtin/gpu/board.tsx +13 -10
- package/src/plugins/builtin/gpu/changes.tsx +4 -4
- package/src/plugins/builtin/gpu/client.ts +19 -1
- package/src/plugins/builtin/gpu/equities.tsx +14 -10
- package/src/plugins/builtin/gpu/headless.ts +56 -25
- package/src/plugins/builtin/gpu/history.tsx +3 -3
- package/src/plugins/builtin/gpu/index.tsx +2 -0
- package/src/plugins/builtin/gpu/model.ts +101 -16
- package/src/plugins/builtin/gpu/pane.tsx +28 -12
- package/src/plugins/builtin/gpu/series.ts +38 -0
- package/src/plugins/builtin/help/function-card.tsx +25 -6
- package/src/plugins/builtin/help/index.tsx +17 -8
- package/src/plugins/builtin/holders/beneficial-model.ts +90 -3
- package/src/plugins/builtin/holders/beneficial-report.ts +57 -5
- package/src/plugins/builtin/holders/beneficial-view.tsx +55 -11
- package/src/plugins/builtin/holders/client.ts +22 -3
- package/src/plugins/builtin/holders/format.ts +18 -3
- package/src/plugins/builtin/holders/headless.ts +36 -133
- package/src/plugins/builtin/holders/pane.tsx +11 -4
- package/src/plugins/builtin/holders/report-header.ts +40 -5
- package/src/plugins/builtin/holders/report-rows.ts +240 -0
- package/src/plugins/builtin/insider/client.ts +35 -20
- package/src/plugins/builtin/insider/headless.ts +41 -11
- package/src/plugins/builtin/insider/index.tsx +26 -9
- package/src/plugins/builtin/insider/insider-data.ts +10 -0
- package/src/plugins/builtin/insider/issuer.ts +49 -0
- package/src/plugins/builtin/insider/model.ts +3 -0
- package/src/plugins/builtin/iv-history/client.ts +14 -4
- package/src/plugins/builtin/iv-history/headless.ts +7 -4
- package/src/plugins/builtin/iv-history/index.tsx +2 -0
- package/src/plugins/builtin/iv-history/model.ts +111 -7
- package/src/plugins/builtin/iv-history/screen-pane.tsx +5 -9
- package/src/plugins/builtin/iv-history/series.ts +29 -0
- package/src/plugins/builtin/jobs/headless.ts +28 -3
- package/src/plugins/builtin/jobs/index.tsx +3 -1
- package/src/plugins/builtin/jobs/series.ts +19 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +2 -1
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +0 -39
- package/src/plugins/builtin/macro-day/client.ts +44 -3
- package/src/plugins/builtin/macro-day/headless.ts +13 -10
- package/src/plugins/builtin/macro-day/model.ts +56 -10
- package/src/plugins/builtin/macro-day/pane.tsx +30 -20
- package/src/plugins/builtin/market-heatmap/model.ts +19 -11
- package/src/plugins/builtin/market-heatmap/portfolio.ts +2 -2
- package/src/plugins/builtin/market-movers/client.ts +7 -5
- package/src/plugins/builtin/market-movers/index.tsx +3 -2
- package/src/plugins/builtin/market-movers/model.ts +30 -0
- package/src/plugins/builtin/market-movers/screener.ts +31 -0
- package/src/plugins/builtin/market-valuation/chart-series.ts +29 -69
- package/src/plugins/builtin/market-valuation/index.tsx +3 -3
- package/src/plugins/builtin/market-valuation/indicators.ts +0 -5
- package/src/plugins/builtin/members/headless.ts +7 -2
- package/src/plugins/builtin/members/model.ts +31 -3
- package/src/plugins/builtin/members/pane.tsx +3 -6
- package/src/plugins/builtin/mna/headless.ts +15 -3
- package/src/plugins/builtin/money-markets/index.tsx +2 -0
- package/src/plugins/builtin/money-markets/series.ts +41 -0
- package/src/plugins/builtin/notes/cli.ts +12 -7
- package/src/plugins/builtin/options/view.tsx +1 -1
- package/src/plugins/builtin/options-calculator/inputs.ts +2 -1
- package/src/plugins/builtin/options-positioning/client.ts +7 -3
- package/src/plugins/builtin/options-positioning/headless.ts +1 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +2 -1
- package/src/plugins/builtin/options-scenario/headless.ts +1 -0
- package/src/plugins/builtin/options-scenario/hedge.ts +2 -1
- package/src/plugins/builtin/perps/client.ts +40 -6
- package/src/plugins/builtin/perps/equity-row.tsx +33 -11
- package/src/plugins/builtin/perps/freshness.ts +66 -0
- package/src/plugins/builtin/perps/headless.ts +45 -30
- package/src/plugins/builtin/perps/index.tsx +18 -1
- package/src/plugins/builtin/perps/model.ts +20 -5
- package/src/plugins/builtin/perps/pane.tsx +76 -20
- package/src/plugins/builtin/perps/series.ts +76 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +67 -61
- package/src/plugins/builtin/plugin-module.ts +3 -0
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +1 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +273 -0
- package/src/plugins/builtin/portfolio-list/cli/import-13f.ts +109 -0
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +331 -65
- package/src/plugins/builtin/portfolio-list/cli/render.ts +200 -31
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +24 -0
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -47
- package/src/plugins/builtin/portfolio-list/column-values.ts +1 -1
- package/src/plugins/builtin/portfolio-list/grid.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/headless.ts +34 -4
- package/src/plugins/builtin/portfolio-list/imported-from.ts +8 -0
- package/src/plugins/builtin/portfolio-list/live-valuation.ts +6 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +17 -1
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +7 -1
- package/src/plugins/builtin/power/client.ts +16 -0
- package/src/plugins/builtin/power/evidence.ts +6 -2
- package/src/plugins/builtin/power/headless.ts +71 -7
- package/src/plugins/builtin/power/index.tsx +4 -1
- package/src/plugins/builtin/power/model.ts +72 -1
- package/src/plugins/builtin/power/pane.tsx +32 -9
- package/src/plugins/builtin/rate-path/headless.ts +11 -3
- package/src/plugins/builtin/realized-vol/headless.ts +5 -0
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -2
- package/src/plugins/builtin/research/event-model.ts +68 -24
- package/src/plugins/builtin/research/events-headless.ts +2 -1
- package/src/plugins/builtin/research/headless.ts +2 -1
- package/src/plugins/builtin/seasonality/client.ts +15 -7
- package/src/plugins/builtin/seasonality/headless.ts +36 -12
- package/src/plugins/builtin/seasonality/index.tsx +2 -2
- package/src/plugins/builtin/seasonality/model.ts +119 -0
- package/src/plugins/builtin/seasonality/pane.tsx +108 -37
- package/src/plugins/builtin/series-inventory.ts +91 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -1
- package/src/plugins/builtin/shared/command-tab.ts +30 -12
- package/src/plugins/builtin/shared/earnings-report.ts +55 -0
- package/src/plugins/builtin/shared/fred-points.ts +20 -0
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +101 -0
- package/src/plugins/builtin/shared/open-description.ts +29 -0
- package/src/plugins/builtin/shared/report-freshness.ts +9 -0
- package/src/plugins/builtin/shared/series-warning.ts +5 -0
- package/src/plugins/builtin/short-interest/headless.ts +1 -0
- package/src/plugins/builtin/short-interest/index.tsx +2 -0
- package/src/plugins/builtin/short-interest/series.ts +36 -0
- package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
- package/src/plugins/builtin/short-interest/watch-model.ts +9 -5
- package/src/plugins/builtin/short-volume/headless.ts +1 -0
- package/src/plugins/builtin/short-volume/index.tsx +2 -0
- package/src/plugins/builtin/short-volume/series.ts +23 -0
- package/src/plugins/builtin/social-mentions/index.tsx +2 -0
- package/src/plugins/builtin/social-mentions/series.ts +23 -0
- package/src/plugins/builtin/supply-chain/graph-pane.tsx +3 -2
- package/src/plugins/builtin/supply-chain/pane.tsx +3 -1
- package/src/plugins/builtin/thirteenf/api.ts +1 -0
- package/src/plugins/builtin/thirteenf/data.ts +6 -4
- package/src/plugins/builtin/thirteenf/headless.ts +217 -36
- package/src/plugins/builtin/thirteenf/model.ts +39 -1
- package/src/plugins/builtin/thirteenf/overlap.ts +75 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +10 -1
- package/src/plugins/builtin/thirteenf/report-notes.ts +103 -1
- package/src/plugins/builtin/thirteenf/types.ts +2 -0
- package/src/plugins/builtin/ticker-detail/index.tsx +3 -0
- package/src/plugins/builtin/ticker-detail/overview/components.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/overview/futures-contract.ts +31 -0
- package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -3
- package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +48 -4
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +13 -3
- package/src/plugins/builtin/time-sales/headless.ts +1 -0
- package/src/plugins/builtin/treasury-auctions/headless.ts +16 -2
- package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
- package/src/plugins/builtin/treasury-auctions/model.ts +8 -0
- package/src/plugins/builtin/vol-surface/headless.ts +1 -0
- package/src/plugins/builtin/volatility/index.tsx +2 -0
- package/src/plugins/builtin/volatility/series.ts +84 -0
- package/src/plugins/builtin/world-venue-map/geo-series.ts +26 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +4 -1
- package/src/plugins/builtin/world-venue-map/index.ts +2 -1
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +8 -4
- package/src/plugins/builtin/world-venue-map/layers.ts +29 -0
- package/src/plugins/builtin/yield-curve/chart.ts +2 -2
- package/src/plugins/builtin/yield-curve/curves.ts +21 -8
- package/src/plugins/builtin/yield-curve/headless.ts +71 -18
- package/src/plugins/builtin/yield-curve/index.tsx +3 -1
- package/src/plugins/builtin/yield-curve/series.ts +86 -0
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/registry/index.ts +89 -4
- package/src/{plugins/builtin/portfolio-list → portfolio}/position-metrics.ts +6 -6
- package/src/portfolio/position-value.ts +40 -0
- package/src/public/__snapshots__/public-api.test.ts.snap +7 -0
- package/src/public/time-series.ts +17 -0
- package/src/react/auto-refresh.ts +13 -3
- package/src/remote/app-host.tsx +20 -4
- package/src/remote/confirm-change.tsx +85 -0
- package/src/remote/controller-utils.ts +8 -1
- package/src/remote/controller.ts +123 -6
- package/src/remote/pane-screenshot.ts +47 -0
- package/src/remote/schema.ts +62 -0
- package/src/remote/types.ts +30 -0
- package/src/remote/watchlist.ts +306 -0
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/worker.ts +1 -1
- package/src/renderers/dom/data-table/frozen-column.ts +3 -1
- package/src/renderers/dom/data-table/index.tsx +33 -6
- package/src/renderers/dom/data-table/row.tsx +27 -1
- package/src/renderers/dom/desktop/control-styles.ts +63 -1
- package/src/renderers/dom/desktop/controls.tsx +30 -23
- package/src/renderers/dom/desktop/dialog-actions.tsx +170 -0
- package/src/renderers/dom/desktop/icons.tsx +7 -0
- package/src/renderers/dom/dom-ui-host.tsx +3 -0
- package/src/renderers/dom/host/box.tsx +3 -0
- package/src/renderers/dom/host/chart-surface.tsx +6 -2
- package/src/renderers/dom/host/live-frame.ts +6 -2
- package/src/renderers/dom/host/overflow-fade.ts +14 -1
- package/src/renderers/dom/host/size-observer.ts +26 -0
- package/src/renderers/dom/host/surface-3d.tsx +7 -2
- package/src/renderers/dom/host/text.tsx +1 -1
- package/src/renderers/dom/styles.css +61 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +17 -2
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +19 -11
- package/src/renderers/electrobun/view/cli-pane-shot-health.ts +10 -26
- package/src/renderers/electrobun/view/main.tsx +2 -0
- package/src/series/capability.ts +75 -0
- package/src/series/csv.ts +160 -0
- package/src/series/files.ts +107 -0
- package/src/series/registry.ts +198 -0
- package/src/series/resolve.ts +107 -0
- package/src/series/standalone.ts +48 -0
- package/src/series/types.ts +145 -0
- package/src/series/validate.ts +171 -0
- package/src/sources/gloomberb-cloud/index.ts +7 -7
- package/src/sources/gloomberb-cloud/normalizers.ts +1 -0
- package/src/sources/history-result.ts +1 -0
- package/src/sources/listing-symbols.ts +4 -1
- package/src/sources/provider-router/batches.ts +4 -1
- package/src/sources/provider-router/cache.ts +4 -2
- package/src/sources/provider-router/cached-routes.ts +1 -1
- package/src/sources/provider-router/financial-routes.ts +1 -0
- package/src/sources/provider-router/financials.ts +13 -0
- package/src/sources/provider-router/history.ts +43 -25
- package/src/sources/provider-router/search.ts +1 -0
- package/src/sync/core-contributors.ts +1 -0
- package/src/tickers/company-name.ts +75 -0
- package/src/tickers/instrument-kind.ts +25 -0
- package/src/tickers/open-target.ts +3 -2
- package/src/tickers/search/index.ts +45 -7
- package/src/tickers/search/ranking.ts +33 -17
- package/src/tickers/search/types.ts +7 -1
- package/src/tickers/us-listing.ts +34 -0
- package/src/time-series/hooks.ts +9 -0
- package/src/time-series/resolve.ts +95 -16
- package/src/time-series/types.ts +4 -2
- package/src/time-series/use-chart-resolution.ts +32 -0
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +5 -0
- package/src/types/financials.ts +52 -1
- package/src/types/headless.ts +61 -0
- package/src/types/plugin.ts +19 -0
- package/src/types/price-history.ts +7 -0
- package/src/types/ticker.ts +14 -0
- package/src/ui/host.tsx +18 -0
- package/src/ui/live-resize.ts +81 -0
- package/src/ui/live-stretch.ts +121 -0
- package/src/ui/measured-cells.ts +58 -0
- package/src/ui/pane-fill.ts +11 -0
- package/src/utils/basis-points.ts +17 -0
- package/src/utils/cli-output.ts +7 -1
- package/src/utils/concurrency.ts +25 -0
- package/src/utils/crypto-pair.ts +20 -0
- package/src/utils/currency-pair.ts +19 -0
- package/src/utils/decimal-comma.ts +19 -0
- package/src/utils/dom-screenshot-css.ts +265 -0
- package/src/utils/dom-screenshot-rasters.ts +276 -0
- package/src/utils/dom-screenshot.ts +437 -52
- package/src/utils/exchange-rate-snapshot.ts +6 -2
- package/src/utils/exchanges.ts +107 -1
- package/src/utils/format.ts +16 -3
- package/src/utils/fundamentals.ts +66 -0
- package/src/utils/futures-generic.ts +6 -12
- package/src/utils/fx-market-hours.ts +15 -5
- package/src/utils/price-history.ts +99 -42
- package/src/utils/sec-eps-basis.ts +24 -11
- package/src/utils/sec.ts +61 -7
- package/src/utils/utc-time.ts +51 -4
- package/src/plugins/builtin/fx-matrix/freshness.ts +0 -65
- /package/src/{plugins/builtin/cloud → tickers}/starter-symbols.ts +0 -0
package/src/cli/listing-arg.ts
CHANGED
|
@@ -2,19 +2,29 @@ import type { DataProvider } from "../types/data-provider";
|
|
|
2
2
|
import type { Quote } from "../types/financials";
|
|
3
3
|
import type { CliCommandContext } from "../types/plugin";
|
|
4
4
|
import type { TickerRecord } from "../types/ticker";
|
|
5
|
-
import { AmbiguousContractError, AmbiguousTickerError, listingChoiceQuery, resolveTickerSearch } from "../tickers/search";
|
|
5
|
+
import { AmbiguousContractError, AmbiguousTickerError, LISTING_SEARCH_LIMIT, listingChoiceQuery, resolveTickerSearch } from "../tickers/search";
|
|
6
|
+
import { findUsListingOfCompany, type UsListing } from "../tickers/us-listing";
|
|
6
7
|
import { cliStyles } from "../utils/cli-output";
|
|
8
|
+
import { groupByCompany, sameCompany, shortCompanyName } from "../tickers/company-name";
|
|
7
9
|
import {
|
|
8
10
|
canonicalExchange,
|
|
11
|
+
exchangeCodeVenues,
|
|
9
12
|
exchangeLabel,
|
|
10
13
|
isKnownExchangeCode,
|
|
14
|
+
isSharedExchangeCode,
|
|
11
15
|
isUsListingExchange,
|
|
12
16
|
parsePublicTickerKey,
|
|
13
17
|
publicTickerKey,
|
|
14
18
|
resolveExchangeTimeZone,
|
|
19
|
+
typedExchangeCode,
|
|
15
20
|
} from "../utils/exchanges";
|
|
16
21
|
import { withMarketData } from "./scoped-context";
|
|
17
22
|
import { isNoProviderMessage } from "../sources/provider-errors";
|
|
23
|
+
import { dashedCoinPair } from "../utils/crypto-pair";
|
|
24
|
+
import { isQuoteStaleForCurrentSession } from "../market-data/quotes/freshness";
|
|
25
|
+
import { formatMarketPriceWithCurrency, quoteFormatOptions } from "../market-data/market/format";
|
|
26
|
+
import { zonedDateKey } from "../utils/zoned-date-time";
|
|
27
|
+
import { bareFuturesCode, futuresContractByCode } from "../market-data/futures-board";
|
|
18
28
|
|
|
19
29
|
/** The `--exchange` option of every command that takes a ticker. */
|
|
20
30
|
export const EXCHANGE_OPTION = {
|
|
@@ -36,6 +46,14 @@ export interface ListingArg {
|
|
|
36
46
|
exchange: string;
|
|
37
47
|
/** SAN:EPA, or SAN when no exchange was named. */
|
|
38
48
|
key: string;
|
|
49
|
+
/**
|
|
50
|
+
* The code as typed when it names several venues (TSE: Toronto or Tokyo),
|
|
51
|
+
* until `resolveCliListing` reads it as the one the symbol trades on.
|
|
52
|
+
* `exchange` is meanwhile the code's first venue.
|
|
53
|
+
*/
|
|
54
|
+
sharedCode?: string;
|
|
55
|
+
/** What a shared code was read as, when not its first venue: "Read 7203:TSE as 7203:JPX: ...". */
|
|
56
|
+
readAs?: string;
|
|
39
57
|
}
|
|
40
58
|
|
|
41
59
|
/**
|
|
@@ -43,26 +61,43 @@ export interface ListingArg {
|
|
|
43
61
|
* an exchange go through the app's alias table, so `SAN:XPAR` is `SAN:EPA`.
|
|
44
62
|
* A symbol's own exchange and `--exchange` that disagree are an error, unless
|
|
45
63
|
* the command takes several symbols and `--exchange` only fills in the ones
|
|
46
|
-
* without an exchange of their own.
|
|
64
|
+
* without an exchange of their own. A code that names several venues (TSE)
|
|
65
|
+
* agrees with any of them. A futures board code no listed security uses is
|
|
66
|
+
* its future: TTF is TTF=F, while NG stays NovaGold.
|
|
47
67
|
*/
|
|
48
68
|
export function parseListingArg(
|
|
49
69
|
raw: string,
|
|
50
70
|
exchangeOption?: string | null,
|
|
51
71
|
{ ownExchangeWins = false }: { ownExchangeWins?: boolean } = {},
|
|
52
72
|
): ListingArg {
|
|
53
|
-
const
|
|
73
|
+
const optionCode = (exchangeOption ?? "").trim().toUpperCase();
|
|
74
|
+
const option = canonicalExchange(optionCode);
|
|
54
75
|
let typed = raw.trim().replace(/^\$/, "").toUpperCase();
|
|
55
76
|
// `SAN:` asks which listing; with --exchange it has its answer.
|
|
56
77
|
if (option && typed.endsWith(":")) typed = typed.slice(0, -1);
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
78
|
+
const venueKey = parsePublicTickerKey(typed);
|
|
79
|
+
// BTC/USD, as crypto venues write a pair, is the coin's own BTC-USD.
|
|
80
|
+
const parsed = { ...venueKey, symbol: dashedCoinPair(venueKey.symbol) ?? venueKey.symbol };
|
|
81
|
+
const ownCode = parsed.exchange ? typedExchangeCode(typed) : "";
|
|
82
|
+
let exchange = parsed.exchange ?? option;
|
|
83
|
+
let code = ownCode || optionCode;
|
|
84
|
+
if (parsed.exchange && option) {
|
|
85
|
+
const named = exchangeCodeVenues(optionCode);
|
|
86
|
+
const agreed = exchangeCodeVenues(ownCode).filter((venue) => named.includes(venue));
|
|
87
|
+
if (agreed.length === 0 && !ownExchangeWins) {
|
|
88
|
+
throw new ListingArgError(
|
|
89
|
+
`${typed} names exchange ${parsed.exchange}, but --exchange names ${option}.`,
|
|
90
|
+
`Name the exchange once, as ${parsed.symbol}:${parsed.exchange} or ${parsed.symbol} --exchange ${option}.`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
// TSE and --exchange JPX agree on Tokyo.
|
|
94
|
+
if (agreed.length === 1) [exchange, code] = [agreed[0]!, agreed[0]!];
|
|
63
95
|
}
|
|
64
|
-
const
|
|
65
|
-
return { symbol: parsed.symbol, exchange, key:
|
|
96
|
+
const shared = isSharedExchangeCode(code) ? { sharedCode: code } : {};
|
|
97
|
+
if (exchange) return { symbol: parsed.symbol, exchange, key: `${parsed.symbol}:${exchange}`, ...shared };
|
|
98
|
+
// A futures board code no security uses is its future: TTF is TTF=F.
|
|
99
|
+
const symbol = bareFuturesCode(parsed.symbol)?.symbol ?? parsed.symbol;
|
|
100
|
+
return { symbol, exchange, key: symbol };
|
|
66
101
|
}
|
|
67
102
|
|
|
68
103
|
interface ListingStore {
|
|
@@ -83,7 +118,9 @@ export interface ListingVenue {
|
|
|
83
118
|
|
|
84
119
|
/**
|
|
85
120
|
* The venues a symbol trades on and the company on each, from the same
|
|
86
|
-
* listing picker the command bar opens for `SAN:`.
|
|
121
|
+
* listing picker the command bar opens for `SAN:`. This is the one venue list
|
|
122
|
+
* every command names; whether a venue it leaves out trades the symbol is the
|
|
123
|
+
* listing's own quote's to say (`notTradedMessage`).
|
|
87
124
|
*/
|
|
88
125
|
export async function listingVenues(symbol: string, { store, dataProvider }: ListingDeps): Promise<ListingVenue[]> {
|
|
89
126
|
const query = `${symbol}:`;
|
|
@@ -91,7 +128,9 @@ export async function listingVenues(symbol: string, { store, dataProvider }: Lis
|
|
|
91
128
|
const tickers = new Map((await store.loadAllTickers()).map((ticker) => [ticker.metadata.ticker.toUpperCase(), ticker] as const));
|
|
92
129
|
let venues: ListingVenue[];
|
|
93
130
|
try {
|
|
94
|
-
const resolved = await resolveTickerSearch({
|
|
131
|
+
const resolved = await resolveTickerSearch({
|
|
132
|
+
query, activeTicker: null, tickers, dataProvider, searchContext: { limit: LISTING_SEARCH_LIMIT },
|
|
133
|
+
});
|
|
95
134
|
const only = !resolved ? null
|
|
96
135
|
: resolved.kind === "local" ? { exchange: resolved.ticker.metadata.exchange, name: resolved.ticker.metadata.name }
|
|
97
136
|
: {
|
|
@@ -114,6 +153,60 @@ function tradesOn(symbol: string, venues: readonly ListingVenue[]): string {
|
|
|
114
153
|
return venues.length > 0 ? ` ${symbol} trades on: ${venues.map((venue) => venue.exchange).join(", ")}.` : "";
|
|
115
154
|
}
|
|
116
155
|
|
|
156
|
+
/** "Toronto Stock Exchange (TSX) or Tokyo Stock Exchange (JPX)". */
|
|
157
|
+
function venueChoices(venues: readonly string[]): string {
|
|
158
|
+
const labels = venues.map(exchangeLabel);
|
|
159
|
+
return labels.length > 1 ? `${labels.slice(0, -1).join(", ")} or ${labels.at(-1)}` : labels[0] ?? "";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* A listing typed with a code that names several venues (7203:TSE), read as
|
|
164
|
+
* the one of them its symbol trades on: the venues the `7203:` picker lists,
|
|
165
|
+
* else the one its own quote prices (search can leave a venue out). The
|
|
166
|
+
* listing says so in `readAs` when that is not the code's first venue. A
|
|
167
|
+
* symbol on several of them asks which; one on none of them says where it
|
|
168
|
+
* trades. When neither search nor a quote knows the symbol, the code's first
|
|
169
|
+
* venue stands and the request's own failure speaks.
|
|
170
|
+
*/
|
|
171
|
+
async function settleSharedCode(listing: ListingArg, deps: ListingDeps): Promise<ListingArg> {
|
|
172
|
+
const code = listing.sharedCode;
|
|
173
|
+
if (!code) return listing;
|
|
174
|
+
const meanings = exchangeCodeVenues(code);
|
|
175
|
+
const venues = await listingVenues(listing.symbol, deps).catch(() => []);
|
|
176
|
+
let traded = meanings.filter((venue) => venues.some((entry) => entry.exchange === venue));
|
|
177
|
+
if (traded.length === 0) {
|
|
178
|
+
const quoted = await Promise.all(meanings.map((exchange) => quotedOnVenue({ symbol: listing.symbol, exchange }, deps)));
|
|
179
|
+
traded = meanings.filter((_venue, index) => quoted[index]);
|
|
180
|
+
}
|
|
181
|
+
const { symbol } = listing;
|
|
182
|
+
if (traded.length > 1) {
|
|
183
|
+
throw new ListingArgError(
|
|
184
|
+
`${code} is ${venueChoices(meanings)}, and ${symbol} trades on ${traded.join(" and ")}.`,
|
|
185
|
+
`Name one: ${traded.map((venue) => `${symbol}:${venue}`).join(" or ")}.`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
if (traded.length === 0) {
|
|
189
|
+
if (venues.length === 0) return listing;
|
|
190
|
+
throw new ListingArgError(`${code} is ${venueChoices(meanings)}, and ${symbol} trades on none of them.${tradesOn(symbol, venues)}`);
|
|
191
|
+
}
|
|
192
|
+
const exchange = traded[0]!;
|
|
193
|
+
const key = `${symbol}:${exchange}`;
|
|
194
|
+
const readAs = exchange === meanings[0] ? {}
|
|
195
|
+
: { readAs: `Read ${symbol}:${code} as ${key}: ${code} is ${venueChoices(meanings)}, and ${symbol} trades on ${exchange}.` };
|
|
196
|
+
return { symbol, exchange, key, ...readAs };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The keys a listing can be saved under: SAN:EPA, or for a code that names
|
|
201
|
+
* several venues (7203:TSE) each of theirs, as local rows are looked up before
|
|
202
|
+
* the code is read.
|
|
203
|
+
*/
|
|
204
|
+
export function listingKeys(listing: ListingArg): string[] {
|
|
205
|
+
return listing.sharedCode
|
|
206
|
+
? exchangeCodeVenues(listing.sharedCode).map((exchange) => `${listing.symbol}:${exchange}`)
|
|
207
|
+
: [listing.key];
|
|
208
|
+
}
|
|
209
|
+
|
|
117
210
|
/**
|
|
118
211
|
* An exchange code the app does not know is accepted only when the symbol is
|
|
119
212
|
* listed there, so a typo never falls back to another venue's line.
|
|
@@ -132,12 +225,31 @@ async function checkListingExchange(listing: ListingArg, deps: ListingDeps): Pro
|
|
|
132
225
|
);
|
|
133
226
|
}
|
|
134
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Whether the listing's own quote prices it on a venue abroad, as `quote
|
|
230
|
+
* SYM:EXCH` reads it. Search can leave a venue out (CAR's ASX line); a quote
|
|
231
|
+
* there settles that it trades. The US exchanges share one symbol, so a quote
|
|
232
|
+
* named for any of them says only that the symbol has a US line: there,
|
|
233
|
+
* search's venues stand.
|
|
234
|
+
*/
|
|
235
|
+
async function quotedOnVenue(listing: Pick<ListingArg, "symbol" | "exchange">, { dataProvider }: ListingDeps): Promise<boolean> {
|
|
236
|
+
if (isUsListingExchange(listing.exchange)) return false;
|
|
237
|
+
try {
|
|
238
|
+
const quote = await dataProvider.getQuote(`${listing.symbol}:${listing.exchange}`, listing.exchange);
|
|
239
|
+
return Number.isFinite(quote.price) && quote.price !== 0
|
|
240
|
+
&& canonicalExchange(quote.listingExchangeName || quote.exchangeName) === listing.exchange;
|
|
241
|
+
} catch {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
135
246
|
/**
|
|
136
247
|
* "SAN does not trade on TSX. SAN trades on: NYSE, EPA." for a listing whose
|
|
137
248
|
* exchange the symbol is not listed on, from the same picker `SAN:` opens.
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
249
|
+
* A venue the picker leaves out but the listing's quote prices (CAR:ASX) is
|
|
250
|
+
* traded, as `quote` says. Null when no exchange was named, when the symbol is
|
|
251
|
+
* listed there, or when its venues cannot be found, so the request's own
|
|
252
|
+
* failure stands. It searches, so ask only after a request came back empty.
|
|
141
253
|
*/
|
|
142
254
|
export async function notTradedMessage(
|
|
143
255
|
listing: Pick<ListingArg, "symbol" | "exchange">,
|
|
@@ -146,6 +258,7 @@ export async function notTradedMessage(
|
|
|
146
258
|
if (!listing.exchange) return null;
|
|
147
259
|
const venues = await listingVenues(listing.symbol, deps).catch(() => []);
|
|
148
260
|
if (venues.length === 0 || venues.some((venue) => venue.exchange === listing.exchange)) return null;
|
|
261
|
+
if (await quotedOnVenue(listing, deps)) return null;
|
|
149
262
|
return `${listing.symbol} does not trade on ${listing.exchange}.${tradesOn(listing.symbol, venues)}`;
|
|
150
263
|
}
|
|
151
264
|
|
|
@@ -165,6 +278,11 @@ export interface ListingDataRequest {
|
|
|
165
278
|
command: string;
|
|
166
279
|
/** What the listing has none of: "history", "a quote". */
|
|
167
280
|
noun: string;
|
|
281
|
+
/**
|
|
282
|
+
* The retry for one listing, when the key does not go right after the
|
|
283
|
+
* command (`gloomberb watchlist add Growth SAN:EPA`); null names the listings alone.
|
|
284
|
+
*/
|
|
285
|
+
retry?: ((key: string) => string) | null;
|
|
168
286
|
}
|
|
169
287
|
|
|
170
288
|
/**
|
|
@@ -181,60 +299,254 @@ type BareListing = Pick<CliListing, "symbol" | "exchange"> & Partial<Pick<CliLis
|
|
|
181
299
|
|
|
182
300
|
/** Where a bare symbol went and where else it trades, in the order they are tried and named. */
|
|
183
301
|
interface BareListingVenues {
|
|
184
|
-
/** Canonical code of the venue the symbol went to. */
|
|
302
|
+
/** Canonical code of the venue the symbol went to; "" when it went nowhere (`choose`). */
|
|
185
303
|
exchange: string;
|
|
186
304
|
/** That venue as search lists it; null for a saved venue search no longer lists. */
|
|
187
305
|
resolved: ListingVenue | null;
|
|
188
|
-
/** The symbol's other venues, in search order
|
|
306
|
+
/** The symbol's other venues, in search order; every venue when `choose`. */
|
|
189
307
|
others: ListingVenue[];
|
|
308
|
+
/** Several companies list the symbol, so no venue is the one it means: the user picks. */
|
|
309
|
+
choose: boolean;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Whether the venues' companies differ by name. A venue with no name is no other company. */
|
|
313
|
+
function listsSeveralCompanies(venues: readonly ListingVenue[]): boolean {
|
|
314
|
+
return groupByCompany(venues, (venue) => venue.name).length > 1;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Every other company that lists the symbol once, at its first venue in search order. */
|
|
318
|
+
function otherCompanies(venues: readonly ListingVenue[], company: string): ListingVenue[] {
|
|
319
|
+
return groupByCompany(venues, (venue) => venue.name)
|
|
320
|
+
.filter((group) => !group.some((venue) => sameCompany(venue.name, company)))
|
|
321
|
+
.map((group) => group[0]!);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* "CAR = Avis Budget Group, NASDAQ (also XETRA, ASX)": the listing a bare
|
|
326
|
+
* symbol several companies list was read on, and where the others list it.
|
|
327
|
+
* Null when one company lists it, so a symbol that means one thing says
|
|
328
|
+
* nothing.
|
|
329
|
+
*/
|
|
330
|
+
export function listingChoiceNote(
|
|
331
|
+
symbol: string,
|
|
332
|
+
chosen: { exchange: string; name?: string | null },
|
|
333
|
+
venues: readonly ListingVenue[],
|
|
334
|
+
): string | null {
|
|
335
|
+
if (!chosen.exchange || !listsSeveralCompanies(venues)) return null;
|
|
336
|
+
const chosenVenue = venues.find((venue) => venue.exchange === chosen.exchange);
|
|
337
|
+
const name = chosen.name?.trim() || chosenVenue?.name || "";
|
|
338
|
+
const others = otherCompanies(venues, chosenVenue?.name || name).map((venue) => venue.exchange)
|
|
339
|
+
.filter((exchange) => exchange !== chosen.exchange);
|
|
340
|
+
if (others.length === 0) return null;
|
|
341
|
+
const more = others.length - MAX_OTHER_LISTINGS;
|
|
342
|
+
const also = `${others.slice(0, MAX_OTHER_LISTINGS).join(", ")}${more > 0 ? ` and ${more} more: gloomberb search ${symbol}` : ""}`;
|
|
343
|
+
return `${symbol} = ${name ? `${shortCompanyName(name)}, ` : ""}${chosen.exchange} (also ${also})`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Quotes of these never name a company's listing, so they are never a choice between companies.
|
|
347
|
+
const UNLISTED_TYPES = new Set(["FUTURE", "INDEX", "CURRENCY", "CRYPTOCURRENCY", "OPTION"]);
|
|
348
|
+
|
|
349
|
+
/** Whether the data service chose the listing: a bare symbol, with no venue named and none saved. */
|
|
350
|
+
function isBareListing(listing: Pick<CliListing, "exchange" | "saved">): boolean {
|
|
351
|
+
return !listing.exchange && !listing.saved;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* The venues of a bare listing, started beside its data request so naming the
|
|
356
|
+
* choice costs no wait; null for a listing whose venue was named or saved.
|
|
357
|
+
*/
|
|
358
|
+
export function loadBareVenues(listing: Pick<CliListing, "symbol" | "exchange" | "saved">, deps: ListingDeps): Promise<ListingVenue[]> | null {
|
|
359
|
+
return isBareListing(listing) ? listingVenues(listing.symbol, deps).catch(() => []) : null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** What the resolver read a typed listing as (7203:TSE as 7203:JPX), for a command's warnings. */
|
|
363
|
+
export function listingReadNotes(listing: Pick<ListingArg, "readAs">): string[] {
|
|
364
|
+
return listing.readAs ? [listing.readAs] : [];
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* `listingChoiceNote` for a listing a command resolved, from the quote it was
|
|
369
|
+
* read on: only a bare symbol went where the data service chose. A named
|
|
370
|
+
* listing whose code names several venues says which it was read as
|
|
371
|
+
* (`listingReadNotes`). A list, so a command can spread it into its warnings.
|
|
372
|
+
*/
|
|
373
|
+
export async function listingChoiceNotes(
|
|
374
|
+
listing: Pick<CliListing, "symbol" | "exchange" | "saved" | "readAs">,
|
|
375
|
+
deps: ListingDeps,
|
|
376
|
+
quote: Pick<Quote, "name" | "listingExchangeName" | "exchangeName" | "instrumentType"> | null | undefined,
|
|
377
|
+
/** The venues `loadBareVenues` already started; looked up here when not given. */
|
|
378
|
+
venues?: Promise<ListingVenue[]> | null,
|
|
379
|
+
): Promise<string[]> {
|
|
380
|
+
if (!isBareListing(listing)) return listingReadNotes(listing);
|
|
381
|
+
if (!quote || UNLISTED_TYPES.has(quote.instrumentType?.trim().toUpperCase() ?? "")) return [];
|
|
382
|
+
const exchange = canonicalExchange(quote.listingExchangeName || quote.exchangeName);
|
|
383
|
+
if (!exchange) return [];
|
|
384
|
+
const listed = await (venues ?? loadBareVenues(listing, deps) ?? []);
|
|
385
|
+
const note = listingChoiceNote(listing.symbol, { exchange, name: quote.name }, listed);
|
|
386
|
+
return note ? [note] : [];
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* `listingChoiceNotes` for a command with no quote of its own to read the
|
|
391
|
+
* listing from (`fn`): it loads the listing's quote, and only for a bare
|
|
392
|
+
* symbol, so a named or saved listing costs nothing.
|
|
393
|
+
*/
|
|
394
|
+
export async function quotedListingChoiceNotes(
|
|
395
|
+
listing: Pick<CliListing, "symbol" | "exchange" | "saved" | "request" | "readAs">,
|
|
396
|
+
deps: ListingDeps,
|
|
397
|
+
): Promise<string[]> {
|
|
398
|
+
const venues = loadBareVenues(listing, deps);
|
|
399
|
+
if (!venues) return listingReadNotes(listing);
|
|
400
|
+
return listingChoiceNotes(listing, deps, await loadListingQuote(deps.dataProvider, listing), venues);
|
|
190
401
|
}
|
|
191
402
|
|
|
192
|
-
/**
|
|
403
|
+
/**
|
|
404
|
+
* The same company's line on a US exchange, under the same symbol: SAN:MCE's
|
|
405
|
+
* New York receipt. Null when the US line is another company (SAN:EPA is
|
|
406
|
+
* Sanofi, SAN in New York is Banco Santander) or there is none.
|
|
407
|
+
*/
|
|
408
|
+
export async function usLineOfListing(
|
|
409
|
+
listing: Pick<ListingArg, "symbol" | "exchange">,
|
|
410
|
+
company: string | null | undefined,
|
|
411
|
+
deps: ListingDeps,
|
|
412
|
+
): Promise<ListingVenue | null> {
|
|
413
|
+
if (!listing.exchange || isUsListingExchange(listing.exchange) || !company) return null;
|
|
414
|
+
const venues = await listingVenues(listing.symbol, deps).catch(() => []);
|
|
415
|
+
return venues.find((venue) => isUsListingExchange(venue.exchange) && sameCompany(venue.name, company)) ?? null;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* The company's own listing on a US exchange: its line under the same symbol
|
|
420
|
+
* (SAN:MCE's New York receipt), else the one search finds by its name
|
|
421
|
+
* (Sanofi's SNY for SAN:EPA, Toyota's TM for 7203:JPX). Funds and contracts
|
|
422
|
+
* that carry its name are not its listing. Null for a listing in the US or
|
|
423
|
+
* with no venue, and when the company has no US line search knows.
|
|
424
|
+
*/
|
|
425
|
+
async function usListingOfCompany(
|
|
426
|
+
listing: Pick<ListingArg, "symbol" | "exchange">,
|
|
427
|
+
company: string | null | undefined,
|
|
428
|
+
deps: ListingDeps,
|
|
429
|
+
): Promise<UsListing | null> {
|
|
430
|
+
const sameSymbol = await usLineOfListing(listing, company, deps);
|
|
431
|
+
if (sameSymbol) return { ...sameSymbol, symbol: listing.symbol };
|
|
432
|
+
if (!listing.exchange || isUsListingExchange(listing.exchange)) return null;
|
|
433
|
+
return findUsListingOfCompany(deps.dataProvider, company);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* What a function whose data covers US listings only (the US earnings board,
|
|
438
|
+
* US options and trades, FINRA short interest) says for a listing abroad,
|
|
439
|
+
* instead of a bare error or another company's numbers under the same
|
|
440
|
+
* symbol: "Time and sales covers US listings only, and SAN:EPA is Sanofi on
|
|
441
|
+
* Euronext Paris (EPA).", then the company's US listing with the command that
|
|
442
|
+
* asks for it ("Sanofi's US listing: SNY (try: gloomberb fn TAS SNY)"), or that
|
|
443
|
+
* none was found.
|
|
444
|
+
*/
|
|
445
|
+
export async function usListingsOnlyMessage(
|
|
446
|
+
what: string,
|
|
447
|
+
listing: Pick<CliListing, "symbol" | "exchange" | "key" | "request" | "saved">,
|
|
448
|
+
deps: ListingDeps,
|
|
449
|
+
retry: (key: string) => string | null,
|
|
450
|
+
): Promise<{ message: string; details: string }> {
|
|
451
|
+
const quote = await loadListingQuote(deps.dataProvider, listing);
|
|
452
|
+
const company = listingIdentity(listing, quote).name;
|
|
453
|
+
const named = company ? `${shortCompanyName(company)} on ${exchangeLabel(listing.exchange)}` : exchangeLabel(listing.exchange);
|
|
454
|
+
const us = await usListingOfCompany(listing, company, deps);
|
|
455
|
+
const owner = company ? shortCompanyName(company) : listing.key;
|
|
456
|
+
const command = us ? retry(us.symbol) : null;
|
|
457
|
+
return {
|
|
458
|
+
message: `${what} covers US listings only, and ${listing.key} is ${named}.`,
|
|
459
|
+
details: us
|
|
460
|
+
? `${owner}'s US listing: ${us.symbol}${command ? ` (try: ${command})` : ""}`
|
|
461
|
+
: `No US listing of ${owner} was found.`,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* A bare symbol goes to its saved listing, else to the one search lists first,
|
|
467
|
+
* unless several companies list it (2222 is Kotobuki Spirits in Tokyo and Saudi
|
|
468
|
+
* Aramco in Riyadh): then it asks, and none of the venues is the answer. Null
|
|
469
|
+
* when it named its exchange.
|
|
470
|
+
*/
|
|
193
471
|
async function bareListingVenues(listing: BareListing, deps: ListingDeps): Promise<BareListingVenues | null> {
|
|
194
472
|
if (listing.exchange) return null;
|
|
195
473
|
const venues = await listingVenues(listing.symbol, deps).catch(() => []);
|
|
196
474
|
const savedExchange = canonicalExchange(listing.saved?.metadata.exchange);
|
|
475
|
+
if (!savedExchange && listsSeveralCompanies(venues)) return { exchange: "", resolved: null, others: venues, choose: true };
|
|
197
476
|
const resolved = venues.find((venue) => venue.exchange === savedExchange) ?? (savedExchange ? null : venues[0] ?? null);
|
|
198
477
|
const exchange = resolved?.exchange ?? savedExchange;
|
|
199
478
|
if (!exchange) return null;
|
|
200
|
-
return { exchange, resolved, others: venues.filter((venue) => venue !== resolved && venue.exchange !== savedExchange) };
|
|
479
|
+
return { exchange, resolved, others: venues.filter((venue) => venue !== resolved && venue.exchange !== savedExchange), choose: false };
|
|
201
480
|
}
|
|
202
481
|
|
|
203
482
|
interface OtherListingsMessage {
|
|
204
483
|
message: string;
|
|
205
484
|
details: string;
|
|
485
|
+
/** What `listings` are, as a sentence names them: "Other listings", or "Listings" when none is the one the symbol went to. */
|
|
486
|
+
heading: string;
|
|
206
487
|
/** SAN:EPA, for each other listing. */
|
|
207
|
-
|
|
488
|
+
listings: string[];
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* The command that asks again for another listing: `gloomberb history SAN:EPA
|
|
493
|
+
* --range 5Y` keeps the options. Null where the request names listings alone.
|
|
494
|
+
*/
|
|
495
|
+
function retryCommand(request: ListingDataRequest, key: string): string | null {
|
|
496
|
+
if (request.retry !== undefined) return request.retry?.(key) ?? null;
|
|
497
|
+
const [command, ...options] = request.command.split(" ");
|
|
498
|
+
return ["gloomberb", command, key, ...options].join(" ");
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* "GC=F Gold future (try: gloomberb quote GC=F)", under a bare futures board
|
|
503
|
+
* code a security keeps (GC, NG) when that security has no data: the future is
|
|
504
|
+
* the other thing the code names. Null for any other symbol.
|
|
505
|
+
*/
|
|
506
|
+
export function futuresTryLine(symbol: string, request: ListingDataRequest): string | null {
|
|
507
|
+
const future = futuresContractByCode(symbol);
|
|
508
|
+
if (!future) return null;
|
|
509
|
+
const retry = retryCommand(request, future.symbol);
|
|
510
|
+
return `${future.symbol} ${future.name} future${retry ? ` (try: ${retry})` : ""}`;
|
|
208
511
|
}
|
|
209
512
|
|
|
210
513
|
function describeOtherListings(
|
|
211
514
|
listing: BareListing,
|
|
212
|
-
{ exchange, resolved, others }: BareListingVenues,
|
|
515
|
+
{ exchange, resolved, others, choose }: BareListingVenues,
|
|
213
516
|
request: ListingDataRequest,
|
|
214
517
|
): OtherListingsMessage | null {
|
|
215
|
-
|
|
518
|
+
const future = futuresTryLine(listing.symbol, request);
|
|
519
|
+
if (others.length === 0 && !future) return null;
|
|
216
520
|
const name = resolved?.name || listing.saved?.metadata.name?.trim() || "";
|
|
217
|
-
const [command, ...options] = request.command.split(" ");
|
|
218
|
-
const retry = (key: string) => ["gloomberb", command, key, ...options].join(" ");
|
|
219
521
|
const lines = others.slice(0, MAX_OTHER_LISTINGS).map((venue) => {
|
|
220
522
|
const key = `${listing.symbol}:${venue.exchange}`;
|
|
221
|
-
|
|
523
|
+
const retry = retryCommand(request, key);
|
|
524
|
+
return `${key}${venue.name ? ` ${venue.name}` : ""}${retry ? ` (try: ${retry})` : ""}`;
|
|
222
525
|
});
|
|
223
526
|
if (others.length > MAX_OTHER_LISTINGS) lines.push(`and ${others.length - MAX_OTHER_LISTINGS} more: gloomberb search ${listing.symbol}`);
|
|
527
|
+
if (future) lines.push(future);
|
|
528
|
+
const heading = choose ? "Listings" : "Other listings";
|
|
224
529
|
return {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
530
|
+
// The symbol went to no venue, so none of them is said to lack the data.
|
|
531
|
+
message: choose
|
|
532
|
+
? `Choose an exchange for ${listing.symbol}.`
|
|
533
|
+
: `${listing.symbol} resolved to ${name ? `${name} (${exchange})` : exchange}, which has no ${request.noun}.`,
|
|
534
|
+
details: lines.length === 1 ? `${heading}: ${lines[0]}` : [`${heading}:`, ...lines.map((line) => ` ${line}`)].join("\n"),
|
|
535
|
+
heading,
|
|
536
|
+
listings: [...others.map((venue) => `${listing.symbol}:${venue.exchange}`), ...future ? [futuresContractByCode(listing.symbol)!.symbol] : []],
|
|
228
537
|
};
|
|
229
538
|
}
|
|
230
539
|
|
|
231
540
|
/**
|
|
232
541
|
* For a bare symbol whose request found no source: the listing it went to and
|
|
233
542
|
* the symbol's other listings, each with the command that asks for it, such
|
|
234
|
-
* as "
|
|
235
|
-
* history." and "
|
|
236
|
-
*
|
|
237
|
-
*
|
|
543
|
+
* as "SXR8 resolved to iShares Core S&P 500 UCITS ETF USD (Acc) (XETRA), which has
|
|
544
|
+
* no history." and "SXR8:BUD iShares Core S&P 500 UCITS ETF USD (Acc) (try:
|
|
545
|
+
* gloomberb history SXR8:BUD)". When several
|
|
546
|
+
* companies list the code, it went to none of them: "Choose an exchange for
|
|
547
|
+
* 2222." and every listing with its retry command, as `2222:` asks. Null when
|
|
548
|
+
* the symbol named its exchange or has no other listing. It searches, so ask
|
|
549
|
+
* only after a request failed.
|
|
238
550
|
*/
|
|
239
551
|
async function otherListingsMessage(
|
|
240
552
|
listing: BareListing,
|
|
@@ -282,15 +594,15 @@ export type BareListingQuote =
|
|
|
282
594
|
/**
|
|
283
595
|
* The quote for a bare symbol no source quoted. The data service reads a
|
|
284
596
|
* symbol with no US listing on its home listing abroad (`bareListingNote`),
|
|
285
|
-
* so this is left for one it reads on none,
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
597
|
+
* so this is left for one it reads on none, and for a home listing with no
|
|
598
|
+
* quote. Search knows where the symbol trades: each listing is asked again by
|
|
599
|
+
* its own key (SXR8:XETRA), as a named one is. The listing the symbol resolved
|
|
600
|
+
* to goes first, then the others together, and the first in search order with a
|
|
601
|
+
* quote is the answer. When none has one, the `otherListingsMessage` failure.
|
|
602
|
+
* A code several companies list (2222) is never answered from one of them: it is
|
|
603
|
+
* that failure, asking which. Null when the symbol named its exchange (a named
|
|
604
|
+
* venue never switches) or search knows no listing of it. It searches, so ask
|
|
605
|
+
* only after a request failed.
|
|
294
606
|
*/
|
|
295
607
|
export async function quoteBareListing(
|
|
296
608
|
listing: CliListing,
|
|
@@ -300,6 +612,10 @@ export async function quoteBareListing(
|
|
|
300
612
|
): Promise<BareListingQuote | null> {
|
|
301
613
|
const venues = await bareListingVenues(listing, deps);
|
|
302
614
|
if (!venues) return null;
|
|
615
|
+
if (venues.choose) {
|
|
616
|
+
const other = describeOtherListings(listing, venues, request);
|
|
617
|
+
return other && { kind: "missing", ...other };
|
|
618
|
+
}
|
|
303
619
|
const first = [namedListing(listing, venues.exchange)];
|
|
304
620
|
const rest = venues.others.map((venue) => namedListing(listing, venue.exchange));
|
|
305
621
|
let tried = first;
|
|
@@ -319,19 +635,49 @@ export async function quoteBareListing(
|
|
|
319
635
|
const others = [...first, ...rest]
|
|
320
636
|
.filter((entry, index) => entry !== answer && (index >= tried.length || quotes[index]))
|
|
321
637
|
.map((entry) => entry.exchange);
|
|
638
|
+
return {
|
|
639
|
+
kind: "quoted",
|
|
640
|
+
listing: answer,
|
|
641
|
+
quote: quotes[found]!,
|
|
642
|
+
note: chosenListingNote(listing.symbol, answer.exchange, others, without),
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* "IEO -> BATS (other listings: IEX, BMV)": the venue a bare symbol went to,
|
|
648
|
+
* the venues it was not quoted on, and the ones it also trades on.
|
|
649
|
+
*/
|
|
650
|
+
export function chosenListingNote(symbol: string, exchange: string, others: readonly string[], without: readonly string[] = []): string {
|
|
322
651
|
const more = others.length - MAX_OTHER_LISTINGS;
|
|
323
652
|
const aside = [
|
|
324
653
|
without.length > 0 ? `no quote on ${without.join(", ")}` : "",
|
|
325
654
|
others.length > 0
|
|
326
|
-
? `other listings: ${others.slice(0, MAX_OTHER_LISTINGS).join(", ")}${more > 0 ? ` and ${more} more: gloomberb search ${
|
|
655
|
+
? `other listings: ${others.slice(0, MAX_OTHER_LISTINGS).join(", ")}${more > 0 ? ` and ${more} more: gloomberb search ${symbol}` : ""}`
|
|
327
656
|
: "",
|
|
328
657
|
].filter(Boolean).join("; ");
|
|
329
|
-
return {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
658
|
+
return `${symbol} -> ${exchange}${aside ? ` (${aside})` : ""}`;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// A quote this much older than now, and stale, is a listing that stopped trading, not a quiet day.
|
|
662
|
+
const ENDED_AFTER_MS = 183 * 24 * 60 * 60_000;
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* The exchange-local date a listing last traded, when its quote is stale and
|
|
666
|
+
* that was over half a year ago (TWTR's 2022-10-27); null for anything newer.
|
|
667
|
+
*/
|
|
668
|
+
function endedTradingDate(quote: Quote, now = Date.now()): string | null {
|
|
669
|
+
const last = Math.max(...[quote.lastTradeTime, quote.lastUpdated].filter((time): time is number => Number.isFinite(time)));
|
|
670
|
+
if (!(last > 0) || now - last < ENDED_AFTER_MS || !isQuoteStaleForCurrentSession(quote, now)) return null;
|
|
671
|
+
const zone = resolveExchangeTimeZone(quote.listingExchangeName || quote.exchangeName) ?? "UTC";
|
|
672
|
+
return zonedDateKey(last, zone);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/** "TWTR: no longer trading since 2022-10-27 (last price $53.86)", for a quote that stopped long ago. */
|
|
676
|
+
export function noLongerTradingNote(label: string, quote: Quote, now = Date.now()): string | null {
|
|
677
|
+
const date = endedTradingDate(quote, now);
|
|
678
|
+
if (!date) return null;
|
|
679
|
+
const price = formatMarketPriceWithCurrency(quote.price, quote.currency, quoteFormatOptions(quote));
|
|
680
|
+
return `${label}: no longer trading since ${date} (last price ${price})`;
|
|
335
681
|
}
|
|
336
682
|
|
|
337
683
|
// The zones of the US exchanges; a venue in another is abroad.
|
|
@@ -393,6 +739,14 @@ type ListingForLoad = Pick<ListingArg, "symbol" | "exchange"> & Partial<Pick<Cli
|
|
|
393
739
|
/** The saved ticker for a listing: under its own key, or a saved symbol whose venue it is. */
|
|
394
740
|
export async function loadSavedListing(store: ListingStore, listing: ListingArg): Promise<TickerRecord | null> {
|
|
395
741
|
if (!listing.exchange) return store.loadTicker(listing.symbol);
|
|
742
|
+
if (listing.sharedCode) {
|
|
743
|
+
// 7203:TSE is the row saved on any venue TSE names.
|
|
744
|
+
for (const exchange of exchangeCodeVenues(listing.sharedCode)) {
|
|
745
|
+
const saved = await loadSavedListing(store, { symbol: listing.symbol, exchange, key: `${listing.symbol}:${exchange}` });
|
|
746
|
+
if (saved) return saved;
|
|
747
|
+
}
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
396
750
|
for (const key of new Set([listing.key, publicTickerKey(listing.symbol, listing.exchange), listing.symbol])) {
|
|
397
751
|
const ticker = await store.loadTicker(key);
|
|
398
752
|
if (ticker && (key === listing.key || canonicalExchange(ticker.metadata.exchange) === listing.exchange)) return ticker;
|
|
@@ -416,10 +770,14 @@ export async function resolveCliListing(
|
|
|
416
770
|
deps: ListingDeps,
|
|
417
771
|
options: { ownExchangeWins?: boolean } = {},
|
|
418
772
|
): Promise<CliListing> {
|
|
419
|
-
const
|
|
420
|
-
if (!
|
|
421
|
-
await checkListingExchange(
|
|
773
|
+
const parsed = parseListingArg(raw, exchangeOption, options);
|
|
774
|
+
if (!parsed.symbol) throw new ListingArgError("Ticker symbol is required.");
|
|
775
|
+
await checkListingExchange(parsed, deps);
|
|
776
|
+
let listing = await settleSharedCode(parsed, deps);
|
|
422
777
|
const saved = await loadSavedListing(deps.store, listing);
|
|
778
|
+
// A code nothing could read stays the venue it is saved on.
|
|
779
|
+
const savedExchange = canonicalExchange(saved?.metadata.exchange);
|
|
780
|
+
if (listing.sharedCode && savedExchange) listing = { symbol: listing.symbol, exchange: savedExchange, key: `${listing.symbol}:${savedExchange}` };
|
|
423
781
|
return {
|
|
424
782
|
...listing,
|
|
425
783
|
saved,
|
|
@@ -456,8 +814,11 @@ export async function requireListingArg(
|
|
|
456
814
|
): Promise<ListingArg> {
|
|
457
815
|
try {
|
|
458
816
|
const listing = parseListingArg(raw, exchangeOption);
|
|
459
|
-
if (listing.symbol.endsWith(":") || (listing.exchange && !isKnownExchangeCode(listing.exchange))) {
|
|
460
|
-
await withMarketData(ctx, (market) =>
|
|
817
|
+
if (listing.symbol.endsWith(":") || (listing.exchange && !isKnownExchangeCode(listing.exchange)) || listing.sharedCode) {
|
|
818
|
+
return await withMarketData(ctx, async (market) => {
|
|
819
|
+
await checkListingExchange(listing, market);
|
|
820
|
+
return settleSharedCode(listing, market);
|
|
821
|
+
});
|
|
461
822
|
}
|
|
462
823
|
return listing;
|
|
463
824
|
} catch (error) {
|
|
@@ -475,7 +836,7 @@ export interface ListingIdentity {
|
|
|
475
836
|
}
|
|
476
837
|
|
|
477
838
|
export function listingIdentity(
|
|
478
|
-
listing: CliListing,
|
|
839
|
+
listing: Pick<CliListing, "symbol" | "exchange" | "saved">,
|
|
479
840
|
quote?: Pick<Quote, "name" | "listingExchangeName" | "exchangeName"> | null,
|
|
480
841
|
): ListingIdentity {
|
|
481
842
|
const quoteExchange = canonicalExchange(quote?.listingExchangeName || quote?.exchangeName);
|
|
@@ -486,7 +847,7 @@ export function listingIdentity(
|
|
|
486
847
|
}
|
|
487
848
|
|
|
488
849
|
/** The listing's own quote, for its company name and venue; null when it has none. */
|
|
489
|
-
export async function loadListingQuote(dataProvider: DataProvider, listing: CliListing): Promise<Quote | null> {
|
|
850
|
+
export async function loadListingQuote(dataProvider: DataProvider, listing: Pick<CliListing, "request">): Promise<Quote | null> {
|
|
490
851
|
try {
|
|
491
852
|
return await dataProvider.getQuote(listing.request.symbol, listing.request.exchange);
|
|
492
853
|
} catch {
|