gloomberb 0.18.0 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api-client/commodity-balances.ts +6 -0
- package/src/api-client/company-kpis.ts +9 -0
- package/src/api-client/crypto-markets.ts +9 -0
- package/src/api-client/data.ts +6 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/types.ts +14 -0
- package/src/app/pane-runtime/plugin-bindings.ts +3 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
- package/src/app/runtime/plugin-bindings.ts +6 -2
- package/src/brokers/logos.ts +66 -0
- package/src/brokers/profile-form.ts +3 -0
- package/src/brokers/remote-broker-adapter.ts +13 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/service-capabilities.ts +7 -1
- package/src/cli/base-converter.ts +60 -18
- package/src/cli/commands/fred-series.ts +15 -5
- package/src/cli/commands/market.ts +290 -159
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/overview.ts +31 -6
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +105 -52
- package/src/cli/currency-hints.ts +27 -0
- package/src/cli/fx-pair.ts +41 -4
- package/src/cli/glossary.ts +57 -4
- package/src/cli/helpers.ts +36 -3
- package/src/cli/index.ts +4 -1
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +37 -2
- package/src/cli/pane-functions/catalog.ts +3 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +9 -4
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +31 -7
- package/src/cli/pane-functions/headless.ts +6 -5
- package/src/cli/pane-functions/index.ts +3 -2
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +2 -1
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/series-search.ts +2 -1
- package/src/cli/symbol-list.ts +51 -16
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +22 -7
- package/src/components/chart/composite/format.ts +13 -1
- package/src/components/chart/composite/level-labels.ts +30 -0
- package/src/components/chart/composite/levels.ts +17 -6
- package/src/components/chart/composite/panel-surface.tsx +64 -16
- package/src/components/chart/composite/price-axis-labels.tsx +2 -1
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/command-bar/workflow/collection-actions.ts +4 -0
- package/src/components/index.ts +2 -0
- package/src/components/layout/pane/content.tsx +16 -14
- package/src/components/layout/pane/footer/index.tsx +22 -3
- package/src/components/layout/pane/footer/model.ts +99 -4
- package/src/components/layout/pane/header-tabs.tsx +15 -1
- package/src/components/onboarding/onboarding-frame.tsx +9 -11
- package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
- package/src/components/onboarding/wizard-model.ts +10 -3
- package/src/components/onboarding/wizard-positions.ts +8 -0
- package/src/components/ui/button.tsx +11 -4
- package/src/components/ui/choice-dialog.tsx +8 -5
- package/src/components/ui/display.tsx +11 -6
- package/src/components/ui/fields.tsx +100 -20
- package/src/components/ui/icon.tsx +6 -1
- package/src/components/ui/list-row-lines.tsx +87 -0
- package/src/components/ui/list-view.tsx +14 -0
- package/src/components/ui/logo-mark.tsx +64 -0
- package/src/components/ui/number-step.ts +54 -0
- package/src/components/ui/select-button.tsx +5 -4
- package/src/components/ui/select-field.tsx +3 -3
- package/src/components/ui/status.tsx +17 -3
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- package/src/core/state/app/instrument.ts +8 -3
- package/src/data/config/store/normalize.ts +4 -1
- package/src/data/plugin-cache.ts +32 -6
- package/src/docs/snapshot.json +57 -22
- package/src/i18n/es.ts +9 -3
- package/src/i18n/ja.ts +9 -3
- package/src/i18n/ko.ts +9 -3
- package/src/i18n/zh-cn.ts +10 -4
- package/src/i18n/zh-tw.ts +10 -4
- package/src/market-data/commodity-names.ts +80 -0
- package/src/market-data/coordinator/auxiliary.ts +3 -1
- package/src/market-data/coordinator/chart.ts +3 -1
- package/src/market-data/futures-board.ts +38 -1
- package/src/market-data/futures-contract.ts +91 -0
- package/src/market-data/fx-closes.ts +78 -1
- package/src/market-data/market/change-basis.ts +219 -0
- package/src/market-data/market/format.ts +28 -12
- package/src/market-data/market/freshness.ts +38 -0
- package/src/market-data/market/status.ts +55 -9
- package/src/market-data/market/year-to-date.ts +46 -0
- package/src/market-data/quotes/contributions.ts +12 -0
- package/src/market-data/quotes/delisting.ts +34 -0
- package/src/market-data/quotes/live-valuation.ts +23 -0
- package/src/market-data/quotes/resolution.ts +8 -2
- package/src/market-data/tax-equivalent.ts +10 -2
- package/src/notifications/app-notifier.ts +2 -2
- package/src/notifications/notification-log.ts +8 -6
- package/src/plugins/builtin/alerts/cli.ts +26 -18
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/analytics/index.tsx +1 -1
- package/src/plugins/builtin/analytics/risk-client.ts +44 -11
- package/src/plugins/builtin/analytics/risk-fx.ts +61 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +10 -1
- package/src/plugins/builtin/analytics/risk-model.ts +79 -16
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/analytics/risk-test-data.ts +3 -0
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- package/src/plugins/builtin/backtest/window.ts +16 -3
- package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
- package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
- package/src/plugins/builtin/broker-manager/index.tsx +20 -9
- package/src/plugins/builtin/broker-manager/model.ts +5 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
- package/src/plugins/builtin/broker-manager/table.ts +5 -1
- package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
- package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
- package/src/plugins/builtin/broker-trading/command.ts +103 -0
- package/src/plugins/builtin/broker-trading/controller.ts +688 -0
- package/src/plugins/builtin/broker-trading/dock.tsx +182 -0
- package/src/plugins/builtin/broker-trading/headless.ts +76 -0
- package/src/plugins/builtin/broker-trading/index.tsx +77 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
- package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
- package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
- package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
- package/src/plugins/builtin/broker-trading/pane.tsx +505 -0
- package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
- package/src/plugins/builtin/broker-trading/ticket.tsx +573 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
- package/src/plugins/builtin/buildout/format.ts +1 -0
- package/src/plugins/builtin/buildout/model/index.ts +16 -7
- package/src/plugins/builtin/buildout/pane/index.tsx +3 -2
- package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
- package/src/plugins/builtin/chart-composer/headless.ts +2 -1
- package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +31 -11
- package/src/plugins/builtin/chart-composer/presets.ts +2 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
- package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
- package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
- package/src/plugins/builtin/cloud/team/pane-sections.tsx +10 -3
- package/src/plugins/builtin/cloud/team/pane.tsx +30 -9
- package/src/plugins/builtin/commodity-balances/client.ts +6 -4
- package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
- package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
- package/src/plugins/builtin/commodity-balances/model.ts +22 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +36 -5
- package/src/plugins/builtin/company-kpis/client.ts +3 -2
- package/src/plugins/builtin/company-kpis/headless.ts +5 -7
- package/src/plugins/builtin/company-kpis/model.ts +85 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +26 -12
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cot/index.tsx +1 -1
- package/src/plugins/builtin/cot/model.ts +35 -12
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
- package/src/plugins/builtin/crypto-board/client.ts +33 -2
- package/src/plugins/builtin/crypto-board/headless.ts +5 -2
- package/src/plugins/builtin/crypto-board/model.ts +28 -8
- package/src/plugins/builtin/custom-view/loader.ts +3 -2
- package/src/plugins/builtin/custom-view/pane.tsx +5 -4
- package/src/plugins/builtin/custom-view/view-spec.ts +1 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
- package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
- package/src/plugins/builtin/econ/calendar-model.ts +38 -2
- package/src/plugins/builtin/econ/fred-catalog.ts +6 -2
- package/src/plugins/builtin/econ/fred-series-map.ts +12 -1
- package/src/plugins/builtin/econ/index.tsx +31 -4
- package/src/plugins/builtin/econ/print-watch.ts +279 -0
- package/src/plugins/builtin/equity-screener/headless.ts +20 -5
- package/src/plugins/builtin/equity-screener/model.ts +40 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +24 -21
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
- package/src/plugins/builtin/executives/data.ts +24 -8
- package/src/plugins/builtin/executives/index.tsx +2 -18
- package/src/plugins/builtin/executives/pane.tsx +34 -10
- package/src/plugins/builtin/exposure/client.ts +4 -1
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/exposure/evidence.ts +4 -1
- package/src/plugins/builtin/exposure/holdings.ts +3 -1
- package/src/plugins/builtin/exposure/pane.tsx +37 -16
- package/src/plugins/builtin/filing-events/index.tsx +9 -3
- package/src/plugins/builtin/futures/table.tsx +6 -3
- package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
- package/src/plugins/builtin/insider/base-values.ts +72 -0
- package/src/plugins/builtin/insider/headless.ts +29 -4
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- package/src/plugins/builtin/market-halts/model.ts +31 -6
- package/src/plugins/builtin/market-halts/pane.tsx +3 -0
- package/src/plugins/builtin/market-movers/client.ts +2 -2
- package/src/plugins/builtin/market-movers/index.tsx +0 -3
- package/src/plugins/builtin/market-movers/session-body.tsx +27 -11
- package/src/plugins/builtin/market-movers/session.ts +25 -10
- package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +33 -13
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/letters/client.ts +1 -0
- package/src/plugins/builtin/news/wire/news/command-bar-search.ts +6 -3
- package/src/plugins/builtin/news/wire/news/detail-view.tsx +2 -1
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -1
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/feed-xml.ts +6 -1
- package/src/plugins/builtin/news/wire/rss/managed-feed.ts +1 -0
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/notes/cli.ts +33 -8
- package/src/plugins/builtin/notes/export.ts +49 -3
- package/src/plugins/builtin/notes/files.ts +14 -3
- package/src/plugins/builtin/notes/index.tsx +3 -3
- package/src/plugins/builtin/notes/quick-notes-pane.tsx +45 -10
- package/src/plugins/builtin/notes/store.ts +13 -3
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/view.tsx +77 -22
- package/src/plugins/builtin/options-calculator/pane.tsx +4 -2
- package/src/plugins/builtin/options-scenario/pane.tsx +41 -6
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -7
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +166 -15
- package/src/plugins/builtin/portfolio-list/cli/render.ts +69 -15
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +2 -2
- package/src/plugins/builtin/portfolio-list/headless.ts +3 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +44 -17
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -1
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +42 -7
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- package/src/plugins/builtin/primary-dealers/client.ts +137 -62
- package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
- package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
- package/src/plugins/builtin/primary-dealers/model.ts +179 -129
- package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
- package/src/plugins/builtin/research/analyst-model.ts +5 -1
- package/src/plugins/builtin/risk-factors/index.tsx +12 -3
- package/src/plugins/builtin/scanner/feed.ts +6 -1
- package/src/plugins/builtin/scanner/headless.ts +1 -1
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/broker-reconnect.ts +105 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
- package/src/plugins/builtin/shared/free-routes.ts +24 -0
- package/src/plugins/builtin/shared/headless-options.ts +11 -0
- package/src/plugins/builtin/shared/pro-feature-wall.tsx +9 -4
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +62 -0
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -2
- package/src/plugins/builtin/themes/model.ts +43 -10
- package/src/plugins/builtin/themes/pane.tsx +8 -6
- package/src/plugins/builtin/thirteenf/api.ts +4 -1
- package/src/plugins/builtin/thirteenf/data.ts +5 -1
- package/src/plugins/builtin/thirteenf/headless.ts +3 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +8 -3
- package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
- package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
- package/src/plugins/builtin/time-sales/pane.tsx +2 -2
- package/src/plugins/builtin/trace-bonds/client.ts +14 -3
- package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +2 -2
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/compat.ts +3 -0
- package/src/plugins/external-runtime.ts +5 -0
- package/src/plugins/loader.ts +6 -0
- package/src/plugins/plugin-icon.ts +61 -0
- package/src/plugins/registry/host-actions.ts +2 -1
- package/src/plugins/registry/index.ts +9 -1
- package/src/plugins/runtime/context.tsx +5 -0
- package/src/plugins/runtime/index.tsx +3 -0
- package/src/portfolio/column-values.ts +13 -4
- package/src/portfolio/cost-fx.ts +61 -0
- package/src/portfolio/mutations.ts +16 -2
- package/src/portfolio/position-metrics.ts +45 -4
- package/src/portfolio/position-quantity.ts +43 -0
- package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
- package/src/public/broker.ts +8 -0
- package/src/public/market-data.ts +5 -0
- package/src/renderers/dom/desktop/control-styles.ts +7 -1
- package/src/renderers/dom/desktop/controls.tsx +39 -7
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/list-view.tsx +51 -19
- package/src/renderers/dom/desktop/menu.tsx +10 -1
- package/src/renderers/dom/desktop/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +6 -0
- package/src/renderers/dom/styles.css +12 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +12 -0
- package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
- package/src/renderers/electrobun/view/external-plugins.ts +1 -0
- package/src/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +27 -2
- package/src/sources/gloomberb-cloud/index.ts +50 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/provider-errors.ts +3 -1
- package/src/sources/provider-router/history.ts +49 -6
- package/src/sources/provider-router/index.ts +2 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +63 -11
- package/src/sync/local-only.ts +121 -0
- package/src/sync/react.ts +6 -0
- package/src/sync/types.ts +3 -0
- package/src/team/collections.ts +2 -0
- package/src/tickers/listing-fate.ts +27 -8
- package/src/tickers/metadata.ts +10 -0
- package/src/tickers/search/index.ts +24 -3
- package/src/time-series/price-comparison.ts +35 -0
- package/src/time-series/resolve.ts +52 -11
- package/src/time-series/spec.ts +1 -1
- package/src/time-series/types.ts +5 -0
- package/src/types/broker.ts +56 -1
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +5 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +12 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/ticker.ts +9 -0
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +2 -1
- package/src/utils/currency-units.ts +41 -1
- package/src/utils/dom-screenshot.ts +22 -9
- package/src/utils/exchange-notation.generated.ts +360 -0
- package/src/utils/exchanges.ts +15 -322
- package/src/utils/format.ts +31 -1
- package/src/utils/instrument-identity.ts +38 -1
- package/src/utils/options.ts +46 -1
- package/src/utils/price-history.ts +4 -0
- package/src/utils/reported-money.ts +8 -4
|
@@ -93,7 +93,7 @@ const POP_OUT = key("p", "op out");
|
|
|
93
93
|
const STEP = key("←/→", " step");
|
|
94
94
|
/** THEM and MEMB members: open them in RRG, CORR, SIW or RIPL, or save them as a watchlist. */
|
|
95
95
|
const MEMBERS_MENU = key(".", " open members in, save as watchlist");
|
|
96
|
-
const CHART_KEYS = [key("s", "eries"), key("i", "ndicators"), key("
|
|
96
|
+
const CHART_KEYS = [key("s", "eries"), key("i", "ndicators"), key("b", "ars"), key("f", "ormulas")];
|
|
97
97
|
|
|
98
98
|
export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
99
99
|
// Research companies
|
|
@@ -445,8 +445,8 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
445
445
|
summary: "Named executive officers and what they were paid, read from the proxy statement and checked against the filing.",
|
|
446
446
|
usage: ["EXEC NVDA"],
|
|
447
447
|
keys: [key("y", "ear"), key("o", "pen filing")],
|
|
448
|
-
data: pro
|
|
449
|
-
access: "
|
|
448
|
+
data: { free: "As filed; each executive's total pay from the latest proxy", pro: "As filed; the pay breakdown and every year" },
|
|
449
|
+
access: "preview",
|
|
450
450
|
bloomberg: ["MGMT"],
|
|
451
451
|
},
|
|
452
452
|
EK: {
|
|
@@ -681,10 +681,10 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
681
681
|
|
|
682
682
|
// Watch markets
|
|
683
683
|
MOST: {
|
|
684
|
-
summary: "Today's top gainers, losers, most active and trending tickers with price, change, volume against average and market cap
|
|
684
|
+
summary: "Today's top gainers, losers, most active and trending tickers with price, change, volume against average and market cap, and the pre-market and after-hours movers. On Pro, the gaps at the open.",
|
|
685
685
|
usage: ["MOST"],
|
|
686
686
|
keys: [TABS, OPEN],
|
|
687
|
-
data: { free: DELAYED, pro:
|
|
687
|
+
data: { free: DELAYED, pro: REAL_TIME },
|
|
688
688
|
bloomberg: ["MOST"],
|
|
689
689
|
},
|
|
690
690
|
WEI: {
|
|
@@ -971,7 +971,7 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
971
971
|
bloomberg: [],
|
|
972
972
|
},
|
|
973
973
|
PDLR: {
|
|
974
|
-
summary: "Weekly primary dealer net positions and
|
|
974
|
+
summary: "Weekly primary dealer net positions with history, by sector and Treasury tenor (bills, coupons, TIPS, FRN), and settlement fails.",
|
|
975
975
|
usage: ["PDLR"],
|
|
976
976
|
keys: [TABS],
|
|
977
977
|
data: same("Weekly, as published"),
|
|
@@ -1110,7 +1110,7 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
1110
1110
|
docs: "PORT",
|
|
1111
1111
|
},
|
|
1112
1112
|
ALRT: {
|
|
1113
|
-
summary: "Your price alerts with current price, distance to target and trigger state, plus alerts
|
|
1113
|
+
summary: "Your price alerts with current price, distance to target and trigger state, plus alerts on earnings, CPI and FOMC dates, filings and 13F, news keywords, analyst changes, 52-week highs and lows, unusual volume, short interest, insider and congress trades, catalysts, IV spikes, options flow and perps.",
|
|
1114
1114
|
usage: ["ALRT"],
|
|
1115
1115
|
keys: [key("a", "dd alert"), key("e", "dit"), key("d", "elete"), key("m", "re-arm")],
|
|
1116
1116
|
data: QUOTES,
|
|
@@ -1238,6 +1238,30 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
1238
1238
|
data: null,
|
|
1239
1239
|
bloomberg: [],
|
|
1240
1240
|
},
|
|
1241
|
+
BUY: {
|
|
1242
|
+
summary: "Open the shared Trade ticket with a buy quantity and optional limit price. Focus starts on Review; choose an account and review the broker preview before confirming.",
|
|
1243
|
+
usage: ["BUY AAPL 10", "BUY AAPL 10 150.25"],
|
|
1244
|
+
keys: [key("r", "eview"), key("o", "rders")],
|
|
1245
|
+
data: same("Broker account and quote"),
|
|
1246
|
+
bloomberg: [],
|
|
1247
|
+
docs: "BR",
|
|
1248
|
+
},
|
|
1249
|
+
SELL: {
|
|
1250
|
+
summary: "Open the shared Trade ticket with a sell quantity and optional limit price. Focus starts on Review; holdings and broker capabilities are checked before confirmation.",
|
|
1251
|
+
usage: ["SELL AAPL 10", "SELL AAPL 10 350.25"],
|
|
1252
|
+
keys: [key("r", "eview"), key("o", "rders")],
|
|
1253
|
+
data: same("Broker account and quote"),
|
|
1254
|
+
bloomberg: [],
|
|
1255
|
+
docs: "BR",
|
|
1256
|
+
},
|
|
1257
|
+
ORD: {
|
|
1258
|
+
summary: "Open orders and recent activity for a selected broker profile and account. Review a modification or confirm cancellation from an open order; uncertain outcomes require reconciliation before another action.",
|
|
1259
|
+
usage: ["ORD"],
|
|
1260
|
+
keys: [key("m", "odify"), key("c", "ancel")],
|
|
1261
|
+
data: same("Broker account snapshot"),
|
|
1262
|
+
bloomberg: [],
|
|
1263
|
+
docs: "BR",
|
|
1264
|
+
},
|
|
1241
1265
|
|
|
1242
1266
|
// Layouts and settings
|
|
1243
1267
|
GL: {
|
|
@@ -168,12 +168,12 @@ async function loadHeadlessPaneModel(
|
|
|
168
168
|
return result;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
type HeadlessMarketContext = Pick<MarketContext, "config" | "store" | "refresh"> & Partial<Pick<MarketContext, "persistence" | "seriesCapabilities">> & {
|
|
171
|
+
type HeadlessMarketContext = Pick<MarketContext, "config" | "store" | "refresh" | "resolveBroker"> & Partial<Pick<MarketContext, "persistence" | "seriesCapabilities">> & {
|
|
172
172
|
dataProvider: HeadlessPaneContext["marketData"];
|
|
173
173
|
};
|
|
174
174
|
|
|
175
175
|
/** What a headless loader reads the market, the account and the saved collections through. */
|
|
176
|
-
function createHeadlessPaneContext(
|
|
176
|
+
export function createHeadlessPaneContext(
|
|
177
177
|
resolved: ResolvedPaneFunction,
|
|
178
178
|
context: HeadlessMarketContext,
|
|
179
179
|
signal: AbortSignal,
|
|
@@ -186,6 +186,7 @@ function createHeadlessPaneContext(
|
|
|
186
186
|
...(context.refresh ? { refresh: true } : {}),
|
|
187
187
|
settings: resolved.instance.settings,
|
|
188
188
|
capabilities: getSharedRegistry() ?? context.seriesCapabilities,
|
|
189
|
+
resolveBroker: context.resolveBroker,
|
|
189
190
|
async resolvePortfolio(id) {
|
|
190
191
|
const portfolio = findNamedCollection(context.config.portfolios, id);
|
|
191
192
|
if (!portfolio) return null;
|
|
@@ -498,7 +499,7 @@ function renderRows(
|
|
|
498
499
|
};
|
|
499
500
|
return renderTable(
|
|
500
501
|
columns.map((column) => ({
|
|
501
|
-
header: displayedHeader(column),
|
|
502
|
+
header: column.textHeader ?? displayedHeader(column),
|
|
502
503
|
align: column.align,
|
|
503
504
|
width: column.width,
|
|
504
505
|
shrink: column.shrink,
|
|
@@ -939,9 +940,9 @@ export async function buildHeadlessFunctionReport(
|
|
|
939
940
|
loaded = await loadResolvedHeadlessPaneModel(resolved, context, rawArgument);
|
|
940
941
|
} catch (error) {
|
|
941
942
|
// A Pro pane the server refused reports what its wall covers, as its rendered view does.
|
|
942
|
-
const locked = lockedHeadlessReport(resolved, error, () => (
|
|
943
|
+
const locked = await lockedHeadlessReport(resolved, error, () => (
|
|
943
944
|
buildHeadlessPaneLoadArgs(resolved.headless!, resolved.token, rawArgument, resolved.options).symbols
|
|
944
|
-
));
|
|
945
|
+
), createHeadlessPaneContext(resolved, context, new AbortController().signal));
|
|
945
946
|
if (locked) return locked;
|
|
946
947
|
throw error;
|
|
947
948
|
}
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
import { isNotATickerMessage } from "../not-a-ticker";
|
|
37
37
|
import { isCliFailure } from "../errors";
|
|
38
38
|
import { isTickerLinkPeer } from "../../layout/ticker-navigation";
|
|
39
|
-
import { knownGapMessage, commandPointer, topicPointer } from "../known-gaps";
|
|
39
|
+
import { knownGapMessage, commandPointer, commandPointerLeads, topicPointer } from "../known-gaps";
|
|
40
40
|
import { selectReportTables } from "../report-tables";
|
|
41
41
|
import { cliOutputWritten } from "../result";
|
|
42
42
|
import type { CatalogCommandHit } from "../unknown-command";
|
|
@@ -486,6 +486,7 @@ export async function runPaneCatalog(args: string[], ctx: CliCommandContext) {
|
|
|
486
486
|
// A setting or a report can be a command, not a function: "light theme" names `config themes` beside THEM,
|
|
487
487
|
// "drip" names `portfolio show --drip` beside DVD.
|
|
488
488
|
const setting = commandPointer(parsed.query);
|
|
489
|
+
const settingLeads = commandPointerLeads(parsed.query);
|
|
489
490
|
// The data catalog series the query names, which `fn G` charts.
|
|
490
491
|
const series = findCatalogSeries(ctx.plugins, parsed.query);
|
|
491
492
|
const listed = pointer
|
|
@@ -496,7 +497,7 @@ export async function runPaneCatalog(args: string[], ctx: CliCommandContext) {
|
|
|
496
497
|
...series.length ? { metadata: { series } } : {},
|
|
497
498
|
...(gap || setting) && ctx.cliOptions.format !== "text" ? { warnings: [gap, setting].filter((note): note is string => !!note) } : {},
|
|
498
499
|
}, {
|
|
499
|
-
text: () => renderPaneCatalogReport(filtered, effectiveParsed, glossary, gap, series, setting),
|
|
500
|
+
text: () => renderPaneCatalogReport(filtered, effectiveParsed, glossary, gap, series, setting, settingLeads),
|
|
500
501
|
});
|
|
501
502
|
} finally {
|
|
502
503
|
registry.destroy();
|
|
@@ -2,6 +2,7 @@ import { refusedLock } from "../../plugins/builtin/shared/cloud-denied";
|
|
|
2
2
|
import { lockNoteReason } from "../../plugins/builtin/shared/pro-lock-note";
|
|
3
3
|
import { cliTerminalWidth, wrapText } from "../../utils/cli-output";
|
|
4
4
|
import { reportFooterLines } from "../report-tables";
|
|
5
|
+
import type { HeadlessPaneContext } from "../../types/headless";
|
|
5
6
|
import type { PaneFunctionReport } from "./report";
|
|
6
7
|
import type { ResolvedPaneFunction } from "./resolver";
|
|
7
8
|
|
|
@@ -22,19 +23,21 @@ export function lockNoteLines(note: string): string[] {
|
|
|
22
23
|
* `reportLockNote`, no source line and `locked` in JSON. The refusal may be
|
|
23
24
|
* the error itself or the cause of the one a headless read wrote for it
|
|
24
25
|
* (`explainRefusal`). Null when the error is no such refusal or the pane has
|
|
25
|
-
* no note, and the error stands.
|
|
26
|
+
* no note, and the error stands. The context lets the note check the free
|
|
27
|
+
* route it names.
|
|
26
28
|
*/
|
|
27
|
-
export function lockedHeadlessReport(
|
|
29
|
+
export async function lockedHeadlessReport(
|
|
28
30
|
resolved: ResolvedPaneFunction,
|
|
29
31
|
error: unknown,
|
|
30
32
|
/** The symbols the report was asked for, read once the error is known to be a refusal. */
|
|
31
33
|
symbolsOf: () => string[],
|
|
32
|
-
|
|
34
|
+
context?: HeadlessPaneContext,
|
|
35
|
+
): Promise<PaneFunctionReport | null> {
|
|
33
36
|
const refused = refusedLock(error);
|
|
34
37
|
if (!refused || !resolved.pane.reportLockNote) return null;
|
|
35
38
|
const gate = refused.gate;
|
|
36
39
|
const symbols = symbolsOf();
|
|
37
|
-
const note = resolved.pane.reportLockNote({ ...refused, symbol: symbols[0] ?? null, token: resolved.token });
|
|
40
|
+
const note = await resolved.pane.reportLockNote({ ...refused, symbol: symbols[0] ?? null, token: resolved.token, ...context ? { context } : {} });
|
|
38
41
|
const reason = lockNoteReason(note);
|
|
39
42
|
return {
|
|
40
43
|
data: {
|
|
@@ -148,7 +148,8 @@ export async function applyListingArgument(
|
|
|
148
148
|
// A list entry with spaces is a company's name search finds, or the symbols it holds.
|
|
149
149
|
const typed = takesOneTicker ? [args.arg] : await readNameEntries(splitSymbolArgs([args.arg]), context.dataProvider);
|
|
150
150
|
if (typed.length === 0) return args;
|
|
151
|
-
|
|
151
|
+
// RY.TO is the listing its quote names, RY:TSX, as `quote` reads it: a function's data is read by venue.
|
|
152
|
+
let listings = await Promise.all(typed.map((raw) => resolveCliListing(raw, exchange, context, { ownExchangeWins: typed.length > 1, venueSuffix: true })));
|
|
152
153
|
// A separator stuck to the end of a pasted list (`UPST;`) is not part of a symbol; it is named.
|
|
153
154
|
const notes: string[] = takesOneTicker ? [] : stripStrayEdges(args.arg).notes;
|
|
154
155
|
const token = found.template?.shortcut?.prefix ?? args.target.toUpperCase();
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { QuoteBatchResult } from "../types/data-provider";
|
|
2
|
+
import type { Quote } from "../types/financials";
|
|
3
|
+
import { bareListingNote } from "../tickers/listing-fate";
|
|
4
|
+
import { notATickerSymbol } from "../sources/provider-errors";
|
|
5
|
+
import { looksLikeCurrencyPair } from "../utils/currency-pair";
|
|
6
|
+
import { canonicalExchange } from "../utils/exchanges";
|
|
7
|
+
import type { CliCommandDef } from "../types/plugin";
|
|
8
|
+
import { currencyCodeNote } from "./currency-hints";
|
|
9
|
+
import { describeSuggestion, suggestListing, suggestUsTicker, type ListingSuggestion } from "./listing-suggestion";
|
|
10
|
+
import { indexRootAlsoLine, indexRootFor, indexRootTryLine } from "./index-roots";
|
|
11
|
+
import { treasuryYieldLine, treasuryYieldTenor } from "./treasury-yields";
|
|
12
|
+
import { knownGapForTicker } from "./known-gaps";
|
|
13
|
+
import {
|
|
14
|
+
futuresTryLine,
|
|
15
|
+
isNoProviderError,
|
|
16
|
+
listingChoiceNotes,
|
|
17
|
+
listingReadNotes,
|
|
18
|
+
loadBareVenues,
|
|
19
|
+
notTradedMessage,
|
|
20
|
+
quoteBareListing,
|
|
21
|
+
requireCliListing,
|
|
22
|
+
tradingBoardNote,
|
|
23
|
+
type CliListing,
|
|
24
|
+
type ListingDataRequest,
|
|
25
|
+
type ListingVenue,
|
|
26
|
+
} from "./listing-arg";
|
|
27
|
+
import { withListingSuggestion } from "./not-a-ticker";
|
|
28
|
+
import type { MarketContext } from "./types";
|
|
29
|
+
|
|
30
|
+
/** The text of an error, or null for none. */
|
|
31
|
+
export function errorMessage(error: unknown): string | null {
|
|
32
|
+
if (error == null) return null;
|
|
33
|
+
return error instanceof Error ? error.message : String(error);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* "APPL is not a US ticker: it is Aptus Pharma Limited on BSE (INR). Did you
|
|
38
|
+
* mean AAPL (Apple Inc.)?", for a symbol read on a listing abroad that is one
|
|
39
|
+
* slip from a US ticker, worded as the not-a-ticker answers word it.
|
|
40
|
+
*/
|
|
41
|
+
export function notUsTickerSentence({ target, quote, listing, usSuggestion }: { target: { symbol: string }; quote: Quote | null; listing?: { name: string | null }; usSuggestion?: ListingSuggestion }): string | null {
|
|
42
|
+
if (!quote || !usSuggestion) return null;
|
|
43
|
+
const venue = canonicalExchange(quote.listingExchangeName || quote.exchangeName);
|
|
44
|
+
const name = quote.name || listing?.name;
|
|
45
|
+
const what = `${name ? `${name} on ` : ""}${venue}${quote.currency ? ` (${quote.currency})` : ""}`;
|
|
46
|
+
return `${target.symbol} is not a US ticker: it is ${what}. Did you mean ${describeSuggestion(usSuggestion)}?`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface QuoteAnswer {
|
|
50
|
+
result: QuoteBatchResult;
|
|
51
|
+
/** The listing the row is: the one asked for, or the venue a bare symbol was quoted on. */
|
|
52
|
+
listing: CliListing;
|
|
53
|
+
/** Why there is no quote, when there is more to say than the source's own error. */
|
|
54
|
+
failure?: { message: string; details?: string; oneLine: string };
|
|
55
|
+
notes: string[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* One symbol's row of `quote` and `compare`. A listing on an exchange its symbol
|
|
60
|
+
* is not listed on says so. A bare symbol the data service read on its home
|
|
61
|
+
* listing abroad names that venue (SXR8 -> XETRA). One no source quotes is
|
|
62
|
+
* quoted on its listings (`quoteBareListing`), else names them, or asks for an
|
|
63
|
+
* exchange when several companies list it (2222); a bare index root (VIX) also
|
|
64
|
+
* names its index (^VIX), and a futures board code a security keeps (GC) its
|
|
65
|
+
* future (GC=F). Venues are only looked up for a
|
|
66
|
+
* symbol with no quote, so a request that succeeds costs no search.
|
|
67
|
+
*/
|
|
68
|
+
export async function answerQuote(
|
|
69
|
+
result: QuoteBatchResult,
|
|
70
|
+
listing: CliListing,
|
|
71
|
+
market: MarketContext,
|
|
72
|
+
request: ListingDataRequest,
|
|
73
|
+
refresh: boolean,
|
|
74
|
+
venues: Promise<ListingVenue[]> | null,
|
|
75
|
+
): Promise<QuoteAnswer> {
|
|
76
|
+
const root = listing.exchange ? null : indexRootFor(listing.symbol);
|
|
77
|
+
// A bare root that quoted something else, such as MOVE (Corvex), points at the index too.
|
|
78
|
+
const also = (quote: Quote) => root && quote.instrumentType?.trim().toUpperCase() !== "INDEX"
|
|
79
|
+
? [indexRootAlsoLine(root, request.command)] : [];
|
|
80
|
+
if (result.quote) {
|
|
81
|
+
// "CAR = Avis Budget Group, NASDAQ (also XETRA, ASX)" names the venue too, so it replaces "SXR8 -> XETRA".
|
|
82
|
+
const choice = venues ? await listingChoiceNotes(listing, market, result.quote, venues) : listingReadNotes(listing);
|
|
83
|
+
const venue = choice.length > 0 ? null : bareListingNote(listing, result.quote);
|
|
84
|
+
// A row saved as 247540:KRX is quoted from KOSDAQ, the board it trades on.
|
|
85
|
+
const board = tradingBoardNote(listing, result.quote);
|
|
86
|
+
return { result, listing, notes: [...choice, venue ?? [], board ?? [], also(result.quote), currencyCodeNote(listing, result.quote) ?? []].flat() };
|
|
87
|
+
}
|
|
88
|
+
const notTraded = await notTradedMessage(listing, market);
|
|
89
|
+
if (notTraded) return { result, listing, failure: { message: notTraded, oneLine: notTraded }, notes: [] };
|
|
90
|
+
if (!isNoProviderError(result.error)) return { result, listing, notes: [] };
|
|
91
|
+
const other = await quoteBareListing(listing, market, request, { refresh });
|
|
92
|
+
if (other?.kind === "quoted") {
|
|
93
|
+
return {
|
|
94
|
+
result: { target: other.listing.request, quote: other.quote },
|
|
95
|
+
listing: other.listing,
|
|
96
|
+
notes: [other.note, ...also(other.quote), currencyCodeNote(listing, other.quote) ?? []].flat(),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// Other listings already name a futures board code's future (GC=F); with none, it is named here.
|
|
100
|
+
// A Treasury yield's root (TNX) names its FRED series and the curve beside its index.
|
|
101
|
+
const yieldTenor = root ? treasuryYieldTenor(listing.symbol) : null;
|
|
102
|
+
const tryLines = [
|
|
103
|
+
yieldTenor ? treasuryYieldLine(yieldTenor) : root ? indexRootTryLine(root, request.command) : null,
|
|
104
|
+
other || listing.exchange ? null : futuresTryLine(listing.symbol, request),
|
|
105
|
+
// BDI asks which exchange (Black Diamond Group) and says nowhere that the Baltic Dry Index is not here.
|
|
106
|
+
listing.exchange ? null : knownGapForTicker(listing.symbol),
|
|
107
|
+
].filter((line): line is string => !!line);
|
|
108
|
+
if (!other && tryLines.length === 0) return { result: await withCurrencyPairHint(result, market), listing, notes: [] };
|
|
109
|
+
const message = other?.message ?? `${listing.symbol} has no ${request.noun}.`;
|
|
110
|
+
const details = [other?.details, ...tryLines].filter(Boolean).join("\n");
|
|
111
|
+
const oneLine = [
|
|
112
|
+
message,
|
|
113
|
+
other ? `${other.heading}: ${other.listings.join(", ")}.` : "",
|
|
114
|
+
...tryLines,
|
|
115
|
+
].filter(Boolean).join(" ");
|
|
116
|
+
return { result, listing, failure: { message, details, oneLine }, notes: [] };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** A listing a command found a quote for, with what `quote` says beside it. */
|
|
120
|
+
interface QuotedListing {
|
|
121
|
+
/** The listing the quote is of: the one typed, or the venue a bare symbol was quoted on. */
|
|
122
|
+
listing: CliListing;
|
|
123
|
+
quote: Quote;
|
|
124
|
+
/** The venue a bare symbol was read on, the currency it may have meant, the US ticker it is one slip from. */
|
|
125
|
+
notes: string[];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The listing a command that keeps a symbol (`alerts add`) was given, read as
|
|
130
|
+
* `quote` reads it: the same resolver and the same quote request, so what is
|
|
131
|
+
* kept is the listing `quote` prints (USDMXN and USD/MXN are USDMXN=X, MXN is
|
|
132
|
+
* MXN:FWB2) and `quote`'s own notes come back with it. A symbol nothing
|
|
133
|
+
* quotes fails the command with why, and the nearest listing search finds when
|
|
134
|
+
* it is near ("Not a ticker: ZZZQQ. Did you mean ZZZQ?"), so nothing is kept
|
|
135
|
+
* for it.
|
|
136
|
+
*/
|
|
137
|
+
export async function requireQuotedListing(
|
|
138
|
+
raw: string,
|
|
139
|
+
exchange: string | null | undefined,
|
|
140
|
+
market: MarketContext,
|
|
141
|
+
ctx: Pick<Parameters<CliCommandDef["execute"]>[1], "fail">,
|
|
142
|
+
command: string,
|
|
143
|
+
): Promise<QuotedListing> {
|
|
144
|
+
const listing = await requireCliListing(raw, exchange, market, ctx);
|
|
145
|
+
const venues = loadBareVenues(listing, market);
|
|
146
|
+
const [result] = await market.dataProvider.getQuotesBatch([listing.request], { revalidate: true });
|
|
147
|
+
const answer = await answerQuote(result ?? { target: listing.request, quote: null }, listing, market, { command, noun: "quote" }, false, venues);
|
|
148
|
+
const quote = answer.result.quote;
|
|
149
|
+
if (!quote) {
|
|
150
|
+
if (answer.failure) ctx.fail(answer.failure.message, answer.failure.details);
|
|
151
|
+
const message = errorMessage(answer.result.error) ?? `No quote found for ${listing.key}.`;
|
|
152
|
+
// The listing search names, else the US ticker it is one slip from (AAPLL for AAPL), which search does not list for a typo.
|
|
153
|
+
const suggestion = notATickerSymbol(message) && !message.includes(" Did you mean ")
|
|
154
|
+
? await suggestListing(listing.symbol, market) ?? await suggestUsTicker(listing.symbol, market)
|
|
155
|
+
: null;
|
|
156
|
+
ctx.fail(suggestion ? withListingSuggestion(message, describeSuggestion(suggestion)) : message);
|
|
157
|
+
}
|
|
158
|
+
// A bare symbol read on a listing abroad that is one slip from a US ticker says so, as `quote` does.
|
|
159
|
+
const venue = answer.listing.saved ? null : bareListingNote(answer.listing, quote);
|
|
160
|
+
const usSuggestion = venue && answer.notes.includes(venue) ? await suggestUsTicker(answer.listing.symbol, market) : null;
|
|
161
|
+
const notUs = notUsTickerSentence({ target: answer.result.target, quote, listing: { name: quote.name ?? null }, usSuggestion: usSuggestion ?? undefined });
|
|
162
|
+
return { listing: answer.listing, quote, notes: [...answer.notes, ...notUs ? [notUs] : []] };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* A symbol written like a currency pair that is not a ticker (USD_NGN, as some
|
|
167
|
+
* FX feeds write a pair) names the listing `gloomberb search` finds nearest to
|
|
168
|
+
* it, as `watchlist add` does: "Did you mean USDNGN=X (US Dollar/Nigerian
|
|
169
|
+
* Naira)?". When search finds nothing near, the row keeps its own error and its
|
|
170
|
+
* search hint.
|
|
171
|
+
*/
|
|
172
|
+
async function withCurrencyPairHint(result: QuoteBatchResult, market: MarketContext): Promise<QuoteBatchResult> {
|
|
173
|
+
const message = errorMessage(result.error);
|
|
174
|
+
const symbol = message ? notATickerSymbol(message) : null;
|
|
175
|
+
if (!message || !symbol || !looksLikeCurrencyPair(symbol)) return result;
|
|
176
|
+
const suggestion = await suggestListing(symbol, market);
|
|
177
|
+
return suggestion ? { ...result, error: withListingSuggestion(message, describeSuggestion(suggestion)) } : result;
|
|
178
|
+
}
|
|
179
|
+
|
package/src/cli/series-search.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { seriesExpression } from "../series/registry";
|
|
2
2
|
import type { SeriesDefinition } from "../series/types";
|
|
3
3
|
import { cliStyles, renderSection, renderTable, wrapText } from "../utils/cli-output";
|
|
4
|
+
import { currencyCodeLabel } from "../utils/currency-units";
|
|
4
5
|
|
|
5
6
|
/** A data catalog series a query names, as the CLI lists it. */
|
|
6
7
|
export interface SeriesMatch {
|
|
@@ -108,7 +109,7 @@ export function renderSeriesMatches(
|
|
|
108
109
|
renderTable(
|
|
109
110
|
[{ header: "Series" }, { header: "Unit" }, { header: "Frequency", optional: true }, { header: "Publisher", optional: true },
|
|
110
111
|
{ header: "Chart", shrink: false }],
|
|
111
|
-
shown.map((match) => [match.name, match.unit, match.frequency, match.publisher ?? "", cliStyles.command(shellArgument(match.expression))]),
|
|
112
|
+
shown.map((match) => [match.name, currencyCodeLabel(match.unit), match.frequency, match.publisher ?? "", cliStyles.command(shellArgument(match.expression))]),
|
|
112
113
|
),
|
|
113
114
|
"",
|
|
114
115
|
...wrapText(hint, width).map((line) => cliStyles.muted(line)),
|
package/src/cli/symbol-list.ts
CHANGED
|
@@ -74,28 +74,59 @@ function looksLikeSymbol(cell: string): boolean {
|
|
|
74
74
|
return SYMBOL_TOKEN.test(cell) && !(NUMBER_CELL.test(cell) && /[.,%]/.test(cell));
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/** How many cells a table's ignored-cells note names before it counts the rest. */
|
|
78
|
+
const IGNORED_PREVIEW = 8;
|
|
79
|
+
|
|
80
|
+
/** What a file or piped text holds: its symbols in order, and the cells of a table read by one column that were left out. */
|
|
81
|
+
interface SymbolText {
|
|
82
|
+
symbols: string[];
|
|
83
|
+
/** Cells beside the symbol column of a table with no header, in reading order; empty for a list or a CSV with a header. */
|
|
84
|
+
ignored: string[];
|
|
85
|
+
}
|
|
86
|
+
|
|
77
87
|
/**
|
|
78
88
|
* The symbols in a file or piped text, in order. A spreadsheet export reads
|
|
79
|
-
* from the column its header names (Symbol, Ticker, Code)
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* are ignored.
|
|
89
|
+
* from the column its header names (Symbol, Ticker, Code). Without a header,
|
|
90
|
+
* every cell is a symbol unless some column after the first holds a name, a
|
|
91
|
+
* price or a weight: that table is read by its first column, and the cells
|
|
92
|
+
* beside it are returned as `ignored` so the command can name them. A list
|
|
93
|
+
* split by commas, spaces or newlines gives every entry, even when a line of
|
|
94
|
+
* it is ragged or carries a stray separator (`"BIMAS ,"`). A byte order mark,
|
|
95
|
+
* Windows line ends, blank lines and lines starting with # or // are ignored.
|
|
83
96
|
*/
|
|
84
|
-
export function parseSymbolText(text: string):
|
|
97
|
+
export function parseSymbolText(text: string): SymbolText {
|
|
85
98
|
const normalized = text.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n");
|
|
86
99
|
const lines = normalized.split("\n");
|
|
87
100
|
const delimiter = detectDelimiter(lines);
|
|
88
101
|
const records = (delimiter ? parseDelimited(normalized, delimiter) : lines.map((line) => line.trim().split(/\s+/)))
|
|
89
102
|
.filter((cells) => cells.some(Boolean) && !isComment(cells[0] ?? ""));
|
|
90
|
-
if (records.length === 0) return [];
|
|
103
|
+
if (records.length === 0) return { symbols: [], ignored: [] };
|
|
91
104
|
const header = records[0]!.map(headerKey);
|
|
92
105
|
const exact = header.findIndex((cell) => SYMBOL_HEADERS.has(cell));
|
|
93
106
|
const column = exact >= 0 ? exact : header.findIndex((cell) => cell.includes("symbol") || cell.includes("ticker"));
|
|
94
|
-
if (column >= 0) return records.slice(1).map((cells) => cells[column] ?? "").filter(Boolean);
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
const grid =
|
|
98
|
-
|
|
107
|
+
if (column >= 0) return { symbols: records.slice(1).map((cells) => cells[column] ?? "").filter(Boolean), ignored: [] };
|
|
108
|
+
// A separator or quote mark stuck to a cell is dropped (and named) when it is read, so it does not make the list a table.
|
|
109
|
+
const cleaned = records.map((cells) => cells.map((cell) => stripStrayEdges(cell).text));
|
|
110
|
+
const grid = cleaned.every((cells) => cells.every((cell, index) => !cell || (looksLikeSymbol(cell) && (index === 0 || !NUMBER_CELL.test(cell)))));
|
|
111
|
+
if (grid) return { symbols: records.flat().filter(Boolean), ignored: [] };
|
|
112
|
+
return {
|
|
113
|
+
symbols: records.map((cells) => cells[0] ?? "").filter(Boolean),
|
|
114
|
+
ignored: records.flatMap((cells) => cells.slice(1)).filter(Boolean),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The note a table read by its first column gets, naming what was left out:
|
|
120
|
+
* `Read the first column as symbols; ignored the 4 cells beside it: Apple
|
|
121
|
+
* Inc., 336.64, Microsoft, 535.07.` Long tables name the first few and count
|
|
122
|
+
* the rest. Null when nothing was ignored.
|
|
123
|
+
*/
|
|
124
|
+
function ignoredCellsNote(ignored: readonly string[]): string | null {
|
|
125
|
+
if (ignored.length === 0) return null;
|
|
126
|
+
const named = ignored.slice(0, IGNORED_PREVIEW).join(", ");
|
|
127
|
+
const more = ignored.length - IGNORED_PREVIEW;
|
|
128
|
+
const cells = ignored.length === 1 ? "the cell" : `the ${ignored.length} cells`;
|
|
129
|
+
return `Read the first column as symbols; ignored ${cells} beside it: ${named}${more > 0 ? ` and ${more} more` : ""}.`;
|
|
99
130
|
}
|
|
100
131
|
|
|
101
132
|
/** Whether standard input is a pipe or a file rather than a terminal, so a command may read its list from it. */
|
|
@@ -138,9 +169,10 @@ export class SymbolListError extends Error {
|
|
|
138
169
|
* standard input when an argument is `-` or nothing else was given and input
|
|
139
170
|
* is piped in. Repeats are dropped. A separator stuck to a symbol in a file, or
|
|
140
171
|
* left on the end of what was typed (`UPST;`), is dropped with the quote marks
|
|
141
|
-
* around it, and
|
|
172
|
+
* around it, and the cells beside the symbols of a table read by its first
|
|
173
|
+
* column are left out; `notes` names each so the command can say so.
|
|
142
174
|
*/
|
|
143
|
-
export async function readSymbolList(source: SymbolListSource): Promise<{ symbols: string[]; repeated: number;
|
|
175
|
+
export async function readSymbolList(source: SymbolListSource): Promise<{ symbols: string[]; repeated: number; notes: string[] }> {
|
|
144
176
|
const readStdin = source.readStdin ?? (() => Bun.stdin.text());
|
|
145
177
|
const wantsStdin = source.args.includes("-") || source.file === "-";
|
|
146
178
|
const typedArgs = source.args.filter((arg) => arg !== "-");
|
|
@@ -148,12 +180,14 @@ export async function readSymbolList(source: SymbolListSource): Promise<{ symbol
|
|
|
148
180
|
const collected = [...typed];
|
|
149
181
|
// A separator left on the end of what was typed: `UPST;`, or a list that ends in a comma.
|
|
150
182
|
const strays = new Set<string>(stripStrayEdges(typedArgs.join(" ")).notes);
|
|
151
|
-
const
|
|
152
|
-
|
|
183
|
+
const ignored: string[] = [];
|
|
184
|
+
const addCells = ({ symbols, ignored: beside }: SymbolText) => {
|
|
185
|
+
for (const cell of symbols) {
|
|
153
186
|
const cleaned = stripStrayEdges(cell);
|
|
154
187
|
cleaned.notes.forEach((note) => strays.add(note));
|
|
155
188
|
collected.push(cleaned.text);
|
|
156
189
|
}
|
|
190
|
+
ignored.push(...beside);
|
|
157
191
|
};
|
|
158
192
|
if (source.file != null && source.file !== "-") {
|
|
159
193
|
if (!source.file.trim() || source.file.startsWith("--")) throw new SymbolListError("--file needs a path, such as --file symbols.csv.");
|
|
@@ -169,5 +203,6 @@ export async function readSymbolList(source: SymbolListSource): Promise<{ symbol
|
|
|
169
203
|
if (wantsStdin || (typed.length === 0 && source.file == null && (source.stdinPiped ?? stdinIsPiped()))) {
|
|
170
204
|
addCells(parseSymbolText(await readStdin()));
|
|
171
205
|
}
|
|
172
|
-
|
|
206
|
+
const ignoredNote = ignoredCellsNote(ignored);
|
|
207
|
+
return { ...uniqueSymbols(collected), notes: [...strays, ...ignoredNote ? [ignoredNote] : []] };
|
|
173
208
|
}
|
package/src/cli/types.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { AssetDataRouter } from "../sources/provider-router";
|
|
|
5
5
|
import type { AppServices } from "../core/app-services";
|
|
6
6
|
import type { CapabilityInvoker } from "../capabilities/types";
|
|
7
7
|
import type { StatementReportSources } from "./statement-filings";
|
|
8
|
+
import type { FxHistoryClient } from "../market-data/fx-closes";
|
|
8
9
|
|
|
9
10
|
export type ConfigContext = {
|
|
10
11
|
config: AppConfig;
|
|
@@ -15,12 +16,15 @@ export type ConfigContext = {
|
|
|
15
16
|
|
|
16
17
|
export type MarketContext = ConfigContext & {
|
|
17
18
|
dataProvider: AssetDataRouter;
|
|
19
|
+
resolveBroker?: (brokerType: string) => import("../types/broker").BrokerAdapter | null;
|
|
18
20
|
/** Charts catalog series in a report run without the plugin runtime. */
|
|
19
21
|
seriesCapabilities?: CapabilityInvoker;
|
|
20
22
|
/** `--refresh`: prefetched data bypasses the local cache. */
|
|
21
23
|
refresh?: boolean;
|
|
22
24
|
/** Filings behind the statements and a bank's figures, read from Gloom Cloud; absent where the data service is a stand-in. */
|
|
23
25
|
statementSources?: StatementReportSources;
|
|
26
|
+
/** Daily FX closes (`fx --date`, converted INS values); Gloom Cloud unless a test stands in for it. */
|
|
27
|
+
fxHistory?: FxHistoryClient;
|
|
24
28
|
};
|
|
25
29
|
|
|
26
30
|
export type CliServicesContext = {
|
|
@@ -11,6 +11,7 @@ import { ChartToolbar, CHART_TOOLBAR_WIDTH } from "./toolbar";
|
|
|
11
11
|
import { AsciiText, Box, ChartSurface, Text, useUiCapabilities, type BoxRenderable } from "../../../ui";
|
|
12
12
|
import { useLiveStretch } from "../../../ui/live-stretch";
|
|
13
13
|
import { useOptionalPaneInstanceId } from "../../../state/app/context";
|
|
14
|
+
import { usePaneFocused } from "../../../state/app/activity";
|
|
14
15
|
import { useTabCursorLink } from "../../../state/app/tab-cursor";
|
|
15
16
|
import { useThemeColors, useThemeId } from "../../../theme/theme-context";
|
|
16
17
|
import { themedSeriesColor, themeSeriesColors } from "../../../theme/series-colors";
|
|
@@ -155,6 +156,8 @@ function ThemedCompositeChart({
|
|
|
155
156
|
const showTextFallback = useShowChartTextFallback();
|
|
156
157
|
const [internalCursorDate, setInternalCursorDate] = useState<Date | null>(null);
|
|
157
158
|
const [toolSpan, setToolSpan] = useState<ChartToolSpan | null>(null);
|
|
159
|
+
const [pointerDragging, setPointerDragging] = useState(false);
|
|
160
|
+
const paneFocused = usePaneFocused();
|
|
158
161
|
const paneInstanceId = useOptionalPaneInstanceId();
|
|
159
162
|
const [drawings, setDrawings] = useState<readonly ChartDrawing[]>(NO_DRAWINGS);
|
|
160
163
|
const [selectedDrawingId, setSelectedDrawingId] = useState<string | null>(null);
|
|
@@ -186,7 +189,13 @@ function ThemedCompositeChart({
|
|
|
186
189
|
(drawing) => drawing.id === selectedDrawingId ? { ...drawing, color } : drawing,
|
|
187
190
|
));
|
|
188
191
|
}, [selectedDrawingId]);
|
|
189
|
-
|
|
192
|
+
// A cursor the owner rests on without following its moves (a seasonal
|
|
193
|
+
// chart's latest week) is where the chart's own cursor goes back to: the
|
|
194
|
+
// pointer moves it as on any chart, and the pointer leaving returns it.
|
|
195
|
+
const restingCursor = cursorDate !== undefined && !onCursorDateChange;
|
|
196
|
+
const ownsCursor = cursorDate === undefined || restingCursor;
|
|
197
|
+
const resolvedCursorDate = cursorDate === undefined ? internalCursorDate
|
|
198
|
+
: restingCursor ? internalCursorDate ?? cursorDate : cursorDate;
|
|
190
199
|
const totalWidth = Math.max(1, Math.floor(width));
|
|
191
200
|
const totalHeight = Math.max(1, Math.floor(height));
|
|
192
201
|
const seriesIdentityRef = useRef<ResolvedSeries[]>([]);
|
|
@@ -327,6 +336,7 @@ function ThemedCompositeChart({
|
|
|
327
336
|
availableAxisWidth,
|
|
328
337
|
Math.max(
|
|
329
338
|
Math.min(MINIMUM_AXIS_LABEL_WIDTH, maximumAxisWidth),
|
|
339
|
+
...(levels?.items ?? []).map((level) => level.axisLabel?.length ?? 0),
|
|
330
340
|
...(layoutPanels ?? []).flatMap((panel) => (["left", "right"] as const).map((side) => {
|
|
331
341
|
const width = compositeAxisLabelWidth(panel.axes[side], formatAxisValue, includeCursorLabels);
|
|
332
342
|
if (!includeSeriesTags || !width) return includeCursorLabels ? width : Math.min(width, maximumAxisWidth);
|
|
@@ -337,7 +347,7 @@ function ThemedCompositeChart({
|
|
|
337
347
|
return (width + 1) * lanes;
|
|
338
348
|
})),
|
|
339
349
|
),
|
|
340
|
-
), [availableAxisWidth, formatAxisValue, includeCursorLabels, includeSeriesTags, layoutPanels, maximumAxisWidth]);
|
|
350
|
+
), [availableAxisWidth, formatAxisValue, includeCursorLabels, includeSeriesTags, layoutPanels, maximumAxisWidth, levels?.items]);
|
|
341
351
|
const leftAxisWidth = hasLeftAxis ? resolvedAxisWidth : 0;
|
|
342
352
|
const rightAxisWidth = hasRightAxis ? resolvedAxisWidth : 0;
|
|
343
353
|
const axisGap = resolvedAxisWidth > 0 ? 1 : 0;
|
|
@@ -417,18 +427,18 @@ function ThemedCompositeChart({
|
|
|
417
427
|
const nextTimestamp = timestamp !== null && Number.isFinite(timestamp) ? timestamp : null;
|
|
418
428
|
if (lastCursorTimestampRef.current === nextTimestamp) return;
|
|
419
429
|
lastCursorTimestampRef.current = nextTimestamp;
|
|
420
|
-
if (
|
|
430
|
+
if (ownsCursor) setInternalCursorDate(date);
|
|
421
431
|
onCursorDateChange?.(date);
|
|
422
432
|
publishCursor?.(date && baseScene && nextTimestamp !== null ? compositeCursorAsOf(baseScene, date) : null);
|
|
423
|
-
}, [baseScene,
|
|
433
|
+
}, [baseScene, onCursorDateChange, ownsCursor, publishCursor]);
|
|
424
434
|
// Another chart taking the cursor, or clearing it, takes it from this one,
|
|
425
435
|
// so it does not come back here later. Read live: a move since the render
|
|
426
436
|
// makes this chart's cursor the shared one again.
|
|
427
437
|
useEffect(() => {
|
|
428
438
|
void holdsSharedCursor;
|
|
429
|
-
if (!link ||
|
|
439
|
+
if (!link || !ownsCursor || lastCursorTimestampRef.current === null || link.held()) return;
|
|
430
440
|
updateCursor(null);
|
|
431
|
-
}, [
|
|
441
|
+
}, [holdsSharedCursor, link, ownsCursor, updateCursor]);
|
|
432
442
|
// Esc on a chart showing another chart's cursor clears it, as on that chart.
|
|
433
443
|
// While the tab's cursor is pinned the arrows move the pin and Esc unpins it.
|
|
434
444
|
const updateKeyboardCursor = useCallback((date: Date | null) => {
|
|
@@ -644,7 +654,11 @@ function ThemedCompositeChart({
|
|
|
644
654
|
);
|
|
645
655
|
}
|
|
646
656
|
|
|
647
|
-
|
|
657
|
+
// The tools belong to the focused pane, and stay while one is in hand or a
|
|
658
|
+
// press drags the plot, so focus moving elsewhere never hides what is
|
|
659
|
+
// drawing. A chart outside a pane (a share page) keeps them.
|
|
660
|
+
const toolbarWanted = paneFocused !== false || armedTool !== null || pointerDragging || toolSpan !== null;
|
|
661
|
+
const showToolbar = interactive && navigable && toolbarWanted && plotWidth > CHART_TOOLBAR_WIDTH + 4;
|
|
648
662
|
// Bars shorter than a day carry a time of day the cursor reads out.
|
|
649
663
|
const intradayBars = scene.timeZone !== undefined || visibleSeries.some((entry) => (
|
|
650
664
|
!!entry.historyResolution && isIntradayResolution(entry.historyResolution)
|
|
@@ -766,6 +780,7 @@ function ThemedCompositeChart({
|
|
|
766
780
|
onZoomViewport={zoomViewport}
|
|
767
781
|
onSetViewport={setViewportRange}
|
|
768
782
|
onToolSpanChange={setToolSpan}
|
|
783
|
+
onDragActiveChange={setPointerDragging}
|
|
769
784
|
keyboardToolDrag={keyboardToolDrag?.panelId === panel.id ? keyboardToolDrag : null}
|
|
770
785
|
onPointerPress={cancelKeyboardPlacement}
|
|
771
786
|
showTextFallback={showTextFallback}
|