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
|
@@ -2,6 +2,9 @@ import { apiClient } from "../../api-client";
|
|
|
2
2
|
import type { NewsArticle } from "../../news/types";
|
|
3
3
|
import { mapCloudNewsArticle } from "../../sources/gloomberb-cloud/news";
|
|
4
4
|
import { cliStyles } from "../../utils/cli-output";
|
|
5
|
+
import { logFailure } from "../../utils/log-failure";
|
|
6
|
+
import { commodityNamedIn, commoditySearchName, isCommodityName } from "../../market-data/commodity-names";
|
|
7
|
+
import type { FuturesContract } from "../../market-data/futures-board";
|
|
5
8
|
|
|
6
9
|
/** What the service accepts of a search: two characters to two hundred, with a letter or a number in them. */
|
|
7
10
|
const MIN_TEXT_LENGTH = 2;
|
|
@@ -60,8 +63,8 @@ export function newsSearchTextProblem(text: string): string | null {
|
|
|
60
63
|
|
|
61
64
|
/**
|
|
62
65
|
* The word a symbol lookup found no stories for, as text to search stories for.
|
|
63
|
-
* Null under three characters, where the
|
|
64
|
-
*
|
|
66
|
+
* Null under three characters, where the word is mostly a piece of other
|
|
67
|
+
* names (ZN in Telomir-Zn) and the lookup's own answer is the better one.
|
|
65
68
|
*/
|
|
66
69
|
export function newsFallbackText(symbol: string): string | null {
|
|
67
70
|
const text = symbol.trim().replace(/^\$/, "");
|
|
@@ -69,15 +72,39 @@ export function newsFallbackText(symbol: string): string | null {
|
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
/**
|
|
72
|
-
* The stories matching the text,
|
|
73
|
-
* first
|
|
74
|
-
*
|
|
75
|
+
* The stories matching the text, in the service's order: stories with every
|
|
76
|
+
* word in the headline first, then the ones with some only in the summary,
|
|
77
|
+
* newest first within each. The service holds stories back from an account
|
|
78
|
+
* without real-time access, as every other news request.
|
|
75
79
|
*/
|
|
76
80
|
export async function searchNewsStories(text: string, limit: number): Promise<NewsArticle[]> {
|
|
77
81
|
const response = await apiClient.searchCloudNews(text, limit);
|
|
78
|
-
return response.items
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
return response.items.map((item) => mapCloudNewsArticle(item));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The futures news route a search offers when no story matches it: `gloomberb news KC=F` for "arabica". */
|
|
86
|
+
export interface CommodityNewsRoute {
|
|
87
|
+
contract: FuturesContract;
|
|
88
|
+
command: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The future of the commodity a search names, offered when no story matched:
|
|
93
|
+
* `news KC=F` searches the stories of coffee, so it is offered only when
|
|
94
|
+
* those are not what just found nothing and they have a story to show.
|
|
95
|
+
*/
|
|
96
|
+
export async function commodityNewsRoute(text: string): Promise<CommodityNewsRoute | null> {
|
|
97
|
+
const contract = commodityNamedIn(text);
|
|
98
|
+
const name = contract ? commoditySearchName(contract) : null;
|
|
99
|
+
if (!contract || !name || isCommodityName(text, name)) return null;
|
|
100
|
+
const stories = await searchNewsStories(name, 1).catch(logFailure("cli.news-search", "Search a commodity's stories failed", []));
|
|
101
|
+
return stories.length > 0 ? { contract, command: `gloomberb news ${contract.symbol}` } : null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** What a search that matched no story says: never a near miss, and the commodity's futures news where it has some. */
|
|
105
|
+
export function noStoriesMessage(text: string, route: CommodityNewsRoute | null): string {
|
|
106
|
+
const none = `No stories match "${text}".`;
|
|
107
|
+
return route ? `${none} For the ${route.contract.name} future, try: ${route.command}` : none;
|
|
81
108
|
}
|
|
82
109
|
|
|
83
110
|
/** The line above the stories: "Stories matching "european gas"", with why a word typed as a symbol is searched. */
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from "../../plugins/builtin/market-movers/model";
|
|
23
23
|
import { MOVER_CANDIDATES } from "../../plugins/builtin/market-movers/client";
|
|
24
24
|
import { fetchEconCalendarFrom } from "../../plugins/builtin/econ/calendar-source";
|
|
25
|
+
import { seriesNotOnFred } from "../../sources/gloomberb-cloud/fred-series";
|
|
25
26
|
import { EXTENDED_SESSION_LABELS, getQuoteSessionFields, type ExtendedSession } from "../../market-data/market/status";
|
|
26
27
|
import {
|
|
27
28
|
COUNTRY_CYCLE,
|
|
@@ -311,9 +312,27 @@ function parseFilter<T extends string>(value: string | undefined, values: readon
|
|
|
311
312
|
return match;
|
|
312
313
|
}
|
|
313
314
|
|
|
315
|
+
/** One of the pane's regions, or any country by its two-letter code (TR, DE), upper-cased. */
|
|
316
|
+
function parseCountry(value: string | undefined, ctx: Parameters<CliCommandDef["execute"]>[1]): CountryFilter {
|
|
317
|
+
if (value == null) return COUNTRY_CYCLE[0]!;
|
|
318
|
+
const text = value.trim();
|
|
319
|
+
const region = COUNTRY_CYCLE.find((candidate) => candidate.toLowerCase() === text.toLowerCase());
|
|
320
|
+
if (region) return region;
|
|
321
|
+
if (/^[a-z]{2}$/i.test(text)) return text.toUpperCase();
|
|
322
|
+
return ctx.fail(`Unknown country "${value}".`, `Use ${COUNTRY_CYCLE.join(", ")}, or a two-letter country code such as TR.`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** A country the calendar lists nothing for says which ones it does list, so a typo (UK for GB) is not read as a quiet week. */
|
|
326
|
+
function unlistedCountryMessage(country: CountryFilter, events: ReadonlyArray<{ country: string }>): string | null {
|
|
327
|
+
if ((COUNTRY_CYCLE as readonly string[]).includes(country)) return null;
|
|
328
|
+
const listed = [...new Set(events.map((event) => event.country).filter((code) => /^[A-Z]{2}$/.test(code)))].sort();
|
|
329
|
+
if (listed.length === 0 || listed.includes(country)) return null;
|
|
330
|
+
return `The calendar lists no ${country} events; it lists ${listed.join(", ")}.`;
|
|
331
|
+
}
|
|
332
|
+
|
|
314
333
|
async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
315
334
|
const rawArgs = [...args];
|
|
316
|
-
const country
|
|
335
|
+
const country = parseCountry(takeOption(rawArgs, "--country"), ctx);
|
|
317
336
|
const impact: ImpactFilter = parseFilter(takeOption(rawArgs, "--impact"), FILTER_CYCLE, "impact", ctx);
|
|
318
337
|
const fromOption = parseDateOption(takeOption(rawArgs, "--from"), "--from", ctx);
|
|
319
338
|
rejectArgs(rawArgs, ECON_USAGE, ctx, "Filter with --country and --impact.");
|
|
@@ -363,7 +382,7 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
363
382
|
}, {
|
|
364
383
|
dateKey: "date",
|
|
365
384
|
defaultLimit: 50,
|
|
366
|
-
empty: econEmptyMessage(from, listedThrough, listedAfter != null),
|
|
385
|
+
empty: unlistedCountryMessage(country, events) ?? econEmptyMessage(from, listedThrough, listedAfter != null),
|
|
367
386
|
...(footnote ? { footnote } : {}),
|
|
368
387
|
columns: [
|
|
369
388
|
// Text shows both halves of the event timestamp in UTC, never the host's zone; exports keep the source values.
|
|
@@ -485,7 +504,7 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
485
504
|
const periodicity = periodicityOf(data.info?.frequency);
|
|
486
505
|
const newestDate = rows.reduce<string | null>((newest, row) => (!newest || row.date > newest ? row.date : newest), null);
|
|
487
506
|
// A newest value FRED does not list yet names its publisher, Treasury for a yield; FRED's own stay "FRED".
|
|
488
|
-
const ahead = fredProvenance(data.observations.filter((observation) => observation.date === newestDate));
|
|
507
|
+
const ahead = fredProvenance(data.observations.filter((observation) => observation.date === newestDate), seriesId);
|
|
489
508
|
// FRED names the series' publisher when Gloom Cloud passes it on, and when it last updated the series.
|
|
490
509
|
const publisher = ahead?.publisher ?? data.info?.source?.trim();
|
|
491
510
|
const publishedAt = ahead ? ahead.publishedAt : fredPublicationTime(data.info, newestDate);
|
|
@@ -498,7 +517,7 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
498
517
|
...(latest ? { latestObservation: exportRow(latest) } : {}),
|
|
499
518
|
},
|
|
500
519
|
freshness: rowsFreshness(rows, {
|
|
501
|
-
source: ahead?.source ?? "FRED", status: "not-a-feed", basis: "published statistics", observedKey: "date", oldest: null,
|
|
520
|
+
source: ahead?.source ?? notOnFredSource(seriesId) ?? "FRED", status: "not-a-feed", basis: "published statistics", observedKey: "date", oldest: null,
|
|
502
521
|
// A monthly or quarterly observation is dated the first of its period, not a day it was seen.
|
|
503
522
|
...(periodicity ? { periodicity } : {}),
|
|
504
523
|
...(publisher ? { publisher } : {}),
|
|
@@ -522,6 +541,12 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
522
541
|
});
|
|
523
542
|
}
|
|
524
543
|
|
|
544
|
+
/** Who a series FRED does not carry comes from, for a source line whose rows do not name it: `TurkStat via the CBRT`. */
|
|
545
|
+
function notOnFredSource(seriesId: string): string | null {
|
|
546
|
+
const elsewhere = seriesNotOnFred(seriesId);
|
|
547
|
+
return elsewhere ? `${elsewhere.publisher} via ${elsewhere.via}` : null;
|
|
548
|
+
}
|
|
549
|
+
|
|
525
550
|
const YIELD_TENORS: Record<string, string> = { DGS3MO: "3M", DGS2: "2Y", DGS10: "10Y", DGS30: "30Y" };
|
|
526
551
|
|
|
527
552
|
async function runYieldCurve(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
@@ -672,11 +697,11 @@ export const overviewCliCommands: CliCommandDef[] = [
|
|
|
672
697
|
group: CLI_COMMAND_GROUPS.markets,
|
|
673
698
|
usage: [ECON_USAGE],
|
|
674
699
|
options: [
|
|
675
|
-
{ flags: "--country <region>", description: "US, G7, EU,
|
|
700
|
+
{ flags: "--country <region>", description: "US, G7, EU, all (default), or one country by its two-letter code, such as TR" },
|
|
676
701
|
{ flags: "--impact <level>", description: "high, medium, low, or all (default all)" },
|
|
677
702
|
{ flags: "--from <date>", description: `First day listed, as ${DATE_OPTION_EXAMPLES} (default today, UTC); an earlier day lists past events from the stored calendar and says where it starts` },
|
|
678
703
|
],
|
|
679
|
-
examples: ["econ", "econ --country US --impact high", "econ --from 2026-10-12 --country US", "econ --tail 20"],
|
|
704
|
+
examples: ["econ", "econ --country US --impact high", "econ --country TR", "econ --from 2026-10-12 --country US", "econ --tail 20"],
|
|
680
705
|
},
|
|
681
706
|
execute: runEcon,
|
|
682
707
|
},
|
|
@@ -5,6 +5,7 @@ import { saveConfig } from "../../data/config/store";
|
|
|
5
5
|
import { builtinPluginGroupMembers } from "../../plugins/ownership";
|
|
6
6
|
import type { AppConfig, TelemetryConfig } from "../../types/config";
|
|
7
7
|
import { canonicalTimeZone } from "../../utils/utc-time";
|
|
8
|
+
import { readBaseCurrency } from "../../utils/base-currency";
|
|
8
9
|
import { NIGHTLY_CHANNEL_SUMMARY, parseUpdateChannel, withUpdateChannel } from "../../updater/channel";
|
|
9
10
|
import type { CliCommandDef } from "../../types/plugin";
|
|
10
11
|
import { resolveCliDataDir, withCliServices, withConfigData } from "../context";
|
|
@@ -65,6 +66,32 @@ function parseTimezoneSetting(value: string): string | null {
|
|
|
65
66
|
);
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
/**
|
|
70
|
+
* The code `config set baseCurrency` stores (`cad` is CAD); fails on a name
|
|
71
|
+
* or sign, naming the code it stands for, and on anything that is no code.
|
|
72
|
+
*/
|
|
73
|
+
function parseBaseCurrencySetting(value: string): string {
|
|
74
|
+
const reading = readBaseCurrency(value);
|
|
75
|
+
if ("code" in reading) return reading.code;
|
|
76
|
+
const [first] = reading.suggestions;
|
|
77
|
+
return fail(reading.error, first
|
|
78
|
+
? `baseCurrency takes an ISO 4217 code: gloomberb config set baseCurrency ${first}`
|
|
79
|
+
: "baseCurrency takes an ISO 4217 code such as USD, EUR or CAD.");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A saved base currency that is no code (an older version stored names), with what to set instead; null for a code. */
|
|
83
|
+
function baseCurrencyProblem(value: unknown): string | null {
|
|
84
|
+
const reading = readBaseCurrency(typeof value === "string" ? value : "");
|
|
85
|
+
if ("code" in reading) return null;
|
|
86
|
+
const [first] = reading.suggestions;
|
|
87
|
+
return `${reading.error} Set one: gloomberb config set baseCurrency ${first ?? "<code>"}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function describeBaseCurrency(value: unknown): string {
|
|
91
|
+
const problem = baseCurrencyProblem(value);
|
|
92
|
+
return problem ? `${String(value)} ${cliStyles.warning(`(${problem})`)}` : String(value);
|
|
93
|
+
}
|
|
94
|
+
|
|
68
95
|
function describeTimezone(value: unknown): string {
|
|
69
96
|
return typeof value === "string" && value ? value : "not set, times print in UTC alone";
|
|
70
97
|
}
|
|
@@ -189,6 +216,9 @@ export function createSystemCliCommands(): CliCommandDef[] {
|
|
|
189
216
|
checks.push({ check: "database", status: "ok", detail: join(services.dataDir, ".gloomberb-cache.db") });
|
|
190
217
|
checks.push({ check: "plugins", status: "ok", detail: String(services.services.pluginRegistry.allPlugins.size) });
|
|
191
218
|
checks.push({ check: "capabilities", status: "ok", detail: String(services.services.pluginRegistry.capabilities.manifests().length) });
|
|
219
|
+
// Only a saved value no conversion can price is worth a line.
|
|
220
|
+
const baseCurrencyIssue = baseCurrencyProblem(services.config.baseCurrency);
|
|
221
|
+
if (baseCurrencyIssue) checks.push({ check: "baseCurrency", status: "warn", detail: baseCurrencyIssue });
|
|
192
222
|
const theme = services.config.theme;
|
|
193
223
|
checks.push({
|
|
194
224
|
check: "theme",
|
|
@@ -311,6 +341,7 @@ export function createSystemCliCommands(): CliCommandDef[] {
|
|
|
311
341
|
...(key === KEYBINDINGS_CONFIG_KEY ? { format: summarizeKeybindings } : {}),
|
|
312
342
|
...(key === "theme" ? { format: (value: unknown) => describeThemeId(String(value)) } : {}),
|
|
313
343
|
...(key === TIMEZONE_CONFIG_KEY ? { format: describeTimezone } : {}),
|
|
344
|
+
...(key === "baseCurrency" ? { format: describeBaseCurrency } : {}),
|
|
314
345
|
})),
|
|
315
346
|
});
|
|
316
347
|
return;
|
|
@@ -329,7 +360,8 @@ export function createSystemCliCommands(): CliCommandDef[] {
|
|
|
329
360
|
}
|
|
330
361
|
ctx.printResult({ data: { key, value: safeConfig[key] } }, {
|
|
331
362
|
text: (data) => key === KEYBINDINGS_CONFIG_KEY ? renderKeybindings(data.value)
|
|
332
|
-
: key === TIMEZONE_CONFIG_KEY ? describeTimezone(data.value)
|
|
363
|
+
: key === TIMEZONE_CONFIG_KEY ? describeTimezone(data.value)
|
|
364
|
+
: key === "baseCurrency" ? describeBaseCurrency(data.value) : describeConfigValue(data.value),
|
|
333
365
|
});
|
|
334
366
|
return;
|
|
335
367
|
}
|
|
@@ -367,6 +399,8 @@ export function createSystemCliCommands(): CliCommandDef[] {
|
|
|
367
399
|
// A display name may come unquoted: config set theme White Phosphor.
|
|
368
400
|
const parsedValue = key === "theme"
|
|
369
401
|
? requireThemeId(args.slice(2).join(" "))
|
|
402
|
+
: key === "baseCurrency"
|
|
403
|
+
? parseBaseCurrencySetting(args.slice(2).join(" "))
|
|
370
404
|
: key === TIMEZONE_CONFIG_KEY
|
|
371
405
|
? parseTimezoneSetting(value)
|
|
372
406
|
: key === UPDATE_CHANNEL_CONFIG_KEY
|
|
@@ -8,11 +8,12 @@ import { describeFundamentalMarketCap, marketCapShareBasisNote, selectMarketCapi
|
|
|
8
8
|
import {
|
|
9
9
|
formatCompact,
|
|
10
10
|
formatCurrency,
|
|
11
|
+
formatCurrencyCode,
|
|
11
12
|
formatNumber,
|
|
12
13
|
formatPercent,
|
|
13
14
|
formatPercentRaw,
|
|
14
15
|
} from "../../utils/format";
|
|
15
|
-
import { formatMarketCostWithCurrency, formatMarketPriceWithCurrency, formatMarketQuantity,
|
|
16
|
+
import { formatMarketCostWithCurrency, formatMarketPriceWithCurrency, formatMarketQuantity, quoteFormatOptions, withCurrencyMinorDigits, type MarketFormatOptions } from "../../market-data/market/format";
|
|
16
17
|
import {
|
|
17
18
|
cliStyles,
|
|
18
19
|
cliTerminalWidth,
|
|
@@ -28,14 +29,16 @@ import {
|
|
|
28
29
|
getExtendedSessionDisplay,
|
|
29
30
|
getQuoteSessionFields,
|
|
30
31
|
getRegularSessionDisplay,
|
|
32
|
+
quoteBookSessionLabel,
|
|
31
33
|
quoteHeadlineTime,
|
|
32
34
|
quoteMarketStateLabel,
|
|
33
35
|
} from "../../market-data/market/status";
|
|
34
36
|
import type { FuturesContractTermsPayload } from "../../api-client/futures-curve";
|
|
35
|
-
import {
|
|
37
|
+
import { changeBasisJson, quoteChangeBasis, quotePreviousCloseDate } from "../../market-data/market/change-basis";
|
|
38
|
+
import { futuresContractLines, futuresSettleText, isFuturesInstrument, loadFuturesContractTerms, minorUnitPricePer } from "../../market-data/futures-contract";
|
|
39
|
+
import { currencyCodeLabel } from "../../utils/currency-units";
|
|
36
40
|
import type { AppConfig } from "../../types/config";
|
|
37
41
|
import type { DeclaredDividendAction, FinancialStatement, Quote, TickerFinancials } from "../../types/financials";
|
|
38
|
-
import { quoteTradingDay } from "../../market-data/quotes/day-range";
|
|
39
42
|
import { atRegularSessionPrice, regularSessionValuationPrice } from "../../market-data/quotes/regular-session-valuation";
|
|
40
43
|
import { computeTickerPriceReturns } from "../../market-data/ticker-price-returns";
|
|
41
44
|
import { instrumentTypeLabel, resolveTickerInstrumentKind } from "../../tickers/instrument-kind";
|
|
@@ -44,9 +47,10 @@ import type { SecFilingItem } from "../../types/data-provider";
|
|
|
44
47
|
import type { NewsArticle } from "../../news/types";
|
|
45
48
|
import type { TickerRecord } from "../../types/ticker";
|
|
46
49
|
import type { CliCommandContext } from "../../types/plugin";
|
|
47
|
-
import { getPortfolioPositionMetrics, getPortfolioQuoteDisplay, resolvePortfolioMarketValue, resolvePortfolioPositionPnl } from "../../portfolio/position-metrics";
|
|
50
|
+
import { getPortfolioPositionMetrics, getPortfolioQuoteDisplay, lotCostFxRate, resolvePortfolioMarketValue, resolvePortfolioPositionPnl } from "../../portfolio/position-metrics";
|
|
48
51
|
import { isFuturesTicker } from "../../portfolio/futures-positions";
|
|
49
|
-
import { createBaseConverter } from "../base-converter";
|
|
52
|
+
import { createBaseConverter, type BaseConversion } from "../base-converter";
|
|
53
|
+
import { formatFxRate } from "../fx-pair";
|
|
50
54
|
import { initMarketData, withMarketData } from "../context";
|
|
51
55
|
import { fail } from "../errors";
|
|
52
56
|
import type { MarketContext } from "../types";
|
|
@@ -58,6 +62,7 @@ import {
|
|
|
58
62
|
formatSignedCurrency,
|
|
59
63
|
formatTimestamp,
|
|
60
64
|
formatWatchlistNames,
|
|
65
|
+
quoteChangeText,
|
|
61
66
|
} from "../helpers";
|
|
62
67
|
import { NotesFiles } from "../../plugins/builtin/notes/files";
|
|
63
68
|
import { isKnownNonUsListing, secFilingItemCodes } from "../../utils/sec";
|
|
@@ -106,10 +111,6 @@ async function loadRecentSecFilings(
|
|
|
106
111
|
// Prose such as a company description stays readable on wide terminals.
|
|
107
112
|
const MAX_PROSE_WIDTH = 100;
|
|
108
113
|
const METADATA_SEPARATOR = " · ";
|
|
109
|
-
const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as const;
|
|
110
|
-
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] as const;
|
|
111
|
-
/** The states the header reads as CLOSED: the venue is between sessions, so nobody is quoting. */
|
|
112
|
-
const CLOSED_MARKET_STATES = new Set<string>(["CLOSED", "PREPRE", "POSTPOST"]);
|
|
113
114
|
|
|
114
115
|
interface TickerCommandDependencies {
|
|
115
116
|
/** `--exchange`: the listing, for a symbol that trades in several places. */
|
|
@@ -149,25 +150,22 @@ function appendMetricSection(lines: string[], title: string, metrics: Array<[str
|
|
|
149
150
|
}
|
|
150
151
|
|
|
151
152
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
153
|
+
* Out of the regular session the bid and ask say whose book they are: the pre-market or after-hours book beside
|
|
154
|
+
* Last and the extended print, or a closed market's last quote with its day (ITA's $202.00 against $208.74
|
|
155
|
+
* around a $206.65 close is no open book).
|
|
154
156
|
*/
|
|
155
157
|
function bidAskText(quote: Quote, options: MarketFormatOptions): string {
|
|
156
158
|
const text = formatBidAsk(quote.bid, quote.ask, quote.bidSize, quote.askSize, quote.currency, options);
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const date = day ? new Date(`${day}T00:00:00Z`) : null;
|
|
160
|
-
const label = date && Number.isFinite(date.getTime())
|
|
161
|
-
? `, ${WEEKDAYS[date.getUTCDay()]} ${date.getUTCDate()} ${MONTHS[date.getUTCMonth()]}` : "";
|
|
162
|
-
return `${text} ${cliStyles.muted(`(last quote${label})`)}`;
|
|
159
|
+
const label = text === "—" ? null : quoteBookSessionLabel(quote);
|
|
160
|
+
return label ? `${text} ${cliStyles.muted(`(${label})`)}` : text;
|
|
163
161
|
}
|
|
164
162
|
|
|
165
163
|
function wrapProse(text: string): string {
|
|
166
164
|
return wrapText(text, Math.min(cliTerminalWidth() ?? MAX_PROSE_WIDTH, MAX_PROSE_WIDTH)).join("\n");
|
|
167
165
|
}
|
|
168
166
|
|
|
169
|
-
function buildStatementMetrics(statement: FinancialStatement, currency?: string): Array<[string, string]> {
|
|
170
|
-
const money = (value: number | undefined, perShare = false) => formatReportedMoney(value, currency, perShare);
|
|
167
|
+
function buildStatementMetrics(statement: FinancialStatement, currency?: string, codes = false): Array<[string, string]> {
|
|
168
|
+
const money = (value: number | undefined, perShare = false) => formatReportedMoney(value, currency, perShare, codes);
|
|
171
169
|
return [
|
|
172
170
|
["Revenue", money(statement.totalRevenue)],
|
|
173
171
|
["Gross Profit", money(statement.grossProfit)],
|
|
@@ -303,8 +301,11 @@ function shouldFetchSecFilings(
|
|
|
303
301
|
}
|
|
304
302
|
|
|
305
303
|
async function appendTickerPositions(lines: string[], tickerFile: TickerRecord | null, quote: TickerFinancials["quote"],
|
|
306
|
-
config: AppConfig, toBase:
|
|
304
|
+
config: AppConfig, toBase: ReportBaseConverter): Promise<void> {
|
|
307
305
|
const quoteCurrency = quote?.currency?.trim() || tickerFile?.metadata.currency?.trim() || "";
|
|
306
|
+
const codes = writesCurrencyCodes(config);
|
|
307
|
+
const money = codes ? formatCurrencyCode : formatCurrency;
|
|
308
|
+
const converted = new Set<string>();
|
|
308
309
|
if (tickerFile && tickerFile.metadata.positions.length > 0) {
|
|
309
310
|
lines.push("");
|
|
310
311
|
lines.push(renderSection("Positions"));
|
|
@@ -318,10 +319,15 @@ async function appendTickerPositions(lines: string[], tickerFile: TickerRecord |
|
|
|
318
319
|
const positionCurrency = metrics.positionCurrency;
|
|
319
320
|
const currentPrice = getPortfolioQuoteDisplay(metrics, quote)?.price ?? null;
|
|
320
321
|
const currentPriceBase = currentPrice != null && quoteCurrency ? await toBase(currentPrice, quoteCurrency) : null;
|
|
321
|
-
const costBasisBase = positionCurrency ? await toBase(metrics.signedCost, positionCurrency) : Number.NaN;
|
|
322
322
|
const positionRate = positionCurrency ? await toBase(1, positionCurrency) : Number.NaN;
|
|
323
|
+
if (positionCurrency) converted.add(positionCurrency.toUpperCase());
|
|
324
|
+
if (quoteCurrency && currentPrice != null) converted.add(quoteCurrency.toUpperCase());
|
|
323
325
|
const baseMetrics = getPortfolioPositionMetrics({ ...tickerFile, metadata: { ...tickerFile.metadata, positions: [position] } }, undefined, quoteCurrency,
|
|
324
326
|
{ currency: config.baseCurrency, convert: value => value * positionRate }, quote);
|
|
327
|
+
// A cost bought at an entered rate into the base currency converts at it, as its P&L does; any other at today's.
|
|
328
|
+
const costBasisBase = !positionCurrency ? Number.NaN
|
|
329
|
+
: lotCostFxRate(position, positionCurrency, config.baseCurrency) !== null ? baseMetrics.signedCost
|
|
330
|
+
: await toBase(metrics.signedCost, positionCurrency);
|
|
325
331
|
const marketValueBase = resolvePortfolioMarketValue(baseMetrics, currentPriceBase)?.net ?? Number.NaN;
|
|
326
332
|
const selectedPnl = resolvePortfolioPositionPnl(baseMetrics, currentPriceBase);
|
|
327
333
|
const pnl = selectedPnl.value;
|
|
@@ -331,26 +337,51 @@ async function appendTickerPositions(lines: string[], tickerFile: TickerRecord |
|
|
|
331
337
|
const stats: CliStatEntry[] = [
|
|
332
338
|
[
|
|
333
339
|
"Position",
|
|
334
|
-
`${formatMarketQuantity(metrics.totalShares, { assetCategory: tickerFile.metadata.assetCategory, multiplier: position.multiplier, priceBasis: metrics.priceBasis, quantityCurrency: positionCurrency })} ${metrics.priceBasis === "percent-of-par" ? "@" : `${tickerFile.metadata.assetCategory === "BOND" ? "units" : contracts ? "contracts" : "shares"} @`} ${positionCurrency ? formatMarketCostWithCurrency(position.avgCost, positionCurrency, { assetCategory: tickerFile.metadata.assetCategory, multiplier: position.multiplier, priceBasis: metrics.priceBasis }) : "—"}`,
|
|
340
|
+
`${formatMarketQuantity(metrics.totalShares, { assetCategory: tickerFile.metadata.assetCategory, multiplier: position.multiplier, priceBasis: metrics.priceBasis, quantityCurrency: positionCurrency })} ${metrics.priceBasis === "percent-of-par" ? "@" : `${tickerFile.metadata.assetCategory === "BOND" ? "units" : contracts ? "contracts" : "shares"} @`} ${positionCurrency ? formatMarketCostWithCurrency(position.avgCost, positionCurrency, { assetCategory: tickerFile.metadata.assetCategory, multiplier: position.multiplier, priceBasis: metrics.priceBasis, currencyCode: codes }) : "—"}`,
|
|
335
341
|
],
|
|
336
|
-
["Cost Basis",
|
|
337
|
-
["Market Value",
|
|
342
|
+
["Cost Basis", money(costBasisBase, config.baseCurrency)],
|
|
343
|
+
["Market Value", money(marketValueBase, config.baseCurrency)],
|
|
338
344
|
[
|
|
339
345
|
selectedPnl.basis === "broker-snapshot" ? "Broker P&L" : "P&L",
|
|
340
|
-
pnl === null ? "—" : colorBySign(formatSignedCurrency(pnl, config.baseCurrency), pnl),
|
|
346
|
+
pnl === null ? "—" : colorBySign(formatSignedCurrency(pnl, config.baseCurrency, codes), pnl),
|
|
341
347
|
],
|
|
342
348
|
];
|
|
343
349
|
if (position.markPrice != null) {
|
|
344
|
-
stats.push(["Broker Mark", positionCurrency ? formatMarketPriceWithCurrency(position.markPrice, positionCurrency, { assetCategory: tickerFile.metadata.assetCategory, multiplier: position.multiplier, priceBasis: metrics.priceBasis }) : "—"]);
|
|
350
|
+
stats.push(["Broker Mark", positionCurrency ? formatMarketPriceWithCurrency(position.markPrice, positionCurrency, { assetCategory: tickerFile.metadata.assetCategory, multiplier: position.multiplier, priceBasis: metrics.priceBasis, currencyCode: codes }) : "—"]);
|
|
345
351
|
}
|
|
346
352
|
lines.push(renderStats(stats));
|
|
347
353
|
if (index < positions.length - 1) {
|
|
348
354
|
lines.push("");
|
|
349
355
|
}
|
|
350
356
|
}
|
|
357
|
+
for (const note of conversionNotes(toBase, converted)) lines.push(cliStyles.muted(note));
|
|
351
358
|
}
|
|
352
359
|
}
|
|
353
360
|
|
|
361
|
+
/** The ticker report's converter: what `createBaseConverter` makes, whose rates a report states; a plain function in tests. */
|
|
362
|
+
type ReportBaseConverter = ((value: number, fromCurrency: string) => Promise<number>) & { conversions?(): BaseConversion[] };
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* A base currency other than USD reads every amount with its code (259.36
|
|
366
|
+
* USD, 878.15B CAD): $ alone is also the Canadian, Australian and other
|
|
367
|
+
* dollars. A USD base keeps the signs it always had.
|
|
368
|
+
*/
|
|
369
|
+
export function writesCurrencyCodes(config: Pick<AppConfig, "baseCurrency"> | undefined): boolean {
|
|
370
|
+
const base = config?.baseCurrency?.trim().toUpperCase();
|
|
371
|
+
return !!base && base !== "USD";
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* "CAD at USD/CAD 1.40623, as of 2026-10-21 04:46 UTC.": the rate and its
|
|
376
|
+
* date for each currency in `currencies` the report converted into the base
|
|
377
|
+
* currency, so a converted figure is never without what it was converted at.
|
|
378
|
+
*/
|
|
379
|
+
function conversionNotes(toBase: ReportBaseConverter, currencies: ReadonlySet<string>): string[] {
|
|
380
|
+
return (toBase.conversions?.() ?? [])
|
|
381
|
+
.filter((conversion) => currencies.has(conversion.currency) && conversion.currency !== conversion.baseCurrency)
|
|
382
|
+
.map(({ currency, baseCurrency, rate, asOf }) => `${baseCurrency} figures at ${currency}/${baseCurrency} ${formatFxRate(rate)}${asOf != null ? `, as of ${formatTimestamp(asOf)}` : ""}.`);
|
|
383
|
+
}
|
|
384
|
+
|
|
354
385
|
/** A source's enterprise value is in the units of the capitalization it reports beside it,
|
|
355
386
|
* which without a declared unit is the listing's quote currency (as the overview labels it). */
|
|
356
387
|
function enterpriseValueCurrency(
|
|
@@ -375,9 +406,9 @@ interface FundamentalsMetric {
|
|
|
375
406
|
|
|
376
407
|
type MetricFigure = Omit<FundamentalsMetric, "label">;
|
|
377
408
|
|
|
378
|
-
/** An amount in the currency the source reported it in; `ccy?` when it named none, as the text says. */
|
|
379
|
-
function reportedMoney(value: number | undefined, currency: string | undefined, perShare = false): MetricFigure {
|
|
380
|
-
return { text: formatReportedMoney(value, currency, perShare), value, unit: currency?.trim() || "ccy?" };
|
|
409
|
+
/** An amount in the currency the source reported it in; `ccy?` when it named none, as the text says. `codes`: per share too (6.54 USD). */
|
|
410
|
+
function reportedMoney(value: number | undefined, currency: string | undefined, perShare = false, codes = false): MetricFigure {
|
|
411
|
+
return { text: formatReportedMoney(value, currency, perShare, codes), value, unit: currency?.trim() || "ccy?" };
|
|
381
412
|
}
|
|
382
413
|
|
|
383
414
|
/** The EV line: labelled "(provider)" when it is more than 5% from market cap + debt - cash, "unavailable" when withdrawn. */
|
|
@@ -567,6 +598,7 @@ function fundamentalsMetrics(
|
|
|
567
598
|
enterpriseValue = reportedMoney(reportedEnterpriseValue(fundamentals), enterpriseValueCurrency(quote, fundamentals)),
|
|
568
599
|
distributions: DividendDistributions | null = null,
|
|
569
600
|
bond: FundBondFacts | null = null,
|
|
601
|
+
codes = false,
|
|
570
602
|
): FundamentalsMetric[] {
|
|
571
603
|
const signed = (value: number) => colorBySign(formatPercent(value), value);
|
|
572
604
|
const receipt = fundamentals?.sharesOutstanding != null && sharesOutstandingInReceipts(quote, fundamentals, profile?.description);
|
|
@@ -578,7 +610,7 @@ function fundamentalsMetrics(
|
|
|
578
610
|
{ label: "Forward P/E", ...multipleFigure(priceEarningsOnEarnings(fundamentals?.forwardPE, fundamentals?.forwardEps)) },
|
|
579
611
|
{ label: "PEG", ...multipleFigure(fundamentals?.pegRatio) },
|
|
580
612
|
// The flows and EPS are one trailing-twelve-month block, the same twelve months for each line.
|
|
581
|
-
{ label: "EPS (TTM)", ...reportedMoney(fundamentals?.eps, fundamentals?.epsCurrency?.trim() || reportingCurrency, true) },
|
|
613
|
+
{ label: "EPS (TTM)", ...reportedMoney(fundamentals?.eps, fundamentals?.epsCurrency?.trim() || reportingCurrency, true, codes) },
|
|
582
614
|
{
|
|
583
615
|
label: dividendYieldLabel(fundamentals?.dividendYieldBasis, bond),
|
|
584
616
|
...forwardYieldFigure(fundamentals, distributions),
|
|
@@ -681,6 +713,8 @@ type FundamentalsReportData = TickerFinancials & {
|
|
|
681
713
|
bank?: BankFiguresPayload | null;
|
|
682
714
|
/** In place of `bank`, for a bank whose figures could not be read. */
|
|
683
715
|
bankNote?: string | null;
|
|
716
|
+
/** A base currency other than USD: every amount reads with its code (`writesCurrencyCodes`). */
|
|
717
|
+
currencyCodes?: boolean;
|
|
684
718
|
};
|
|
685
719
|
|
|
686
720
|
/** "Latest quarter 2026-06-30, 10-Q filed 2026-08-06." and the newer quarter's results release, for the lines under a report's figures. */
|
|
@@ -720,7 +754,10 @@ function valuationPriceMetrics(financials: FundamentalsReportData): Fundamentals
|
|
|
720
754
|
const quote = financials.quote;
|
|
721
755
|
const session = getRegularSessionDisplay(quote);
|
|
722
756
|
if (!quote || !session) return [];
|
|
723
|
-
const options = withCurrencyMinorDigits(
|
|
757
|
+
const options = withCurrencyMinorDigits({
|
|
758
|
+
...quoteFormatOptions(quote, undefined, financials.quoteMetadata?.instrumentType),
|
|
759
|
+
...financials.currencyCodes ? { currencyCode: true } : {},
|
|
760
|
+
}, quote.currency);
|
|
724
761
|
const price = (value: number) => formatMarketPriceWithCurrency(value, quote.currency, options);
|
|
725
762
|
const extended = getExtendedSessionDisplay(quote);
|
|
726
763
|
const extendedLabel = extended ? EXTENDED_SESSION_LABELS[extended.session] : "";
|
|
@@ -729,7 +766,8 @@ function valuationPriceMetrics(financials: FundamentalsReportData): Fundamentals
|
|
|
729
766
|
session.changePercent != null && Number.isFinite(session.changePercent)
|
|
730
767
|
? {
|
|
731
768
|
label: "Change",
|
|
732
|
-
|
|
769
|
+
// With what the move is measured from, as `ticker` and `quote` say it: "vs Fri 16 Oct close".
|
|
770
|
+
text: quoteChangeText(quote, session, options),
|
|
733
771
|
value: session.changePercent,
|
|
734
772
|
unit: "%",
|
|
735
773
|
}
|
|
@@ -764,7 +802,7 @@ function fundamentalsReportMetrics(snapshot: FundamentalsReportData, view: "fund
|
|
|
764
802
|
? { text: `${formatCompact(capitalization.value)} ${capitalization.currency}`, value: capitalization.value, unit: capitalization.currency }
|
|
765
803
|
: { text: "—" };
|
|
766
804
|
const metrics = fundamentalsMetrics(quote, fundamentals, financials.profile, marketCap, computeTickerPriceReturns(financials), fundamentalsCurrency(financials),
|
|
767
|
-
undefined, snapshot.distributions ?? null, snapshot.bondFund ?? null);
|
|
805
|
+
undefined, snapshot.distributions ?? null, snapshot.bondFund ?? null, snapshot.currencyCodes === true);
|
|
768
806
|
if (view === "fundamentals") {
|
|
769
807
|
return [...metrics, ...floatAndShortInterestMetrics(quote, fundamentals, financials.profile)]
|
|
770
808
|
.filter((metric) => metric.text !== "—");
|
|
@@ -924,7 +962,7 @@ export async function buildTickerReport({
|
|
|
924
962
|
tickerFile: TickerRecord | null;
|
|
925
963
|
financials: TickerFinancials;
|
|
926
964
|
config: AppConfig;
|
|
927
|
-
toBase:
|
|
965
|
+
toBase: ReportBaseConverter;
|
|
928
966
|
notes?: string;
|
|
929
967
|
/** Why there is no quote, when a source said; "Quote unavailable." otherwise. */
|
|
930
968
|
quoteNote?: string;
|
|
@@ -941,8 +979,10 @@ export async function buildTickerReport({
|
|
|
941
979
|
const profile = financials.profile;
|
|
942
980
|
const name = quote?.name || tickerFile?.metadata.name || symbol;
|
|
943
981
|
const baseQuoteOptions = quoteFormatOptions(quote, tickerFile?.metadata.assetCategory, financials.quoteMetadata?.instrumentType);
|
|
982
|
+
const baseCurrency = config.baseCurrency.trim().toUpperCase();
|
|
983
|
+
const codes = writesCurrencyCodes(config);
|
|
944
984
|
// Pad money prices to the currency's minor unit so a range reads £35.10 - £35.485, never past it (JPY has none).
|
|
945
|
-
const quoteOptions = withCurrencyMinorDigits(baseQuoteOptions, quote?.currency);
|
|
985
|
+
const quoteOptions = withCurrencyMinorDigits(codes ? { ...baseQuoteOptions, currencyCode: true } : baseQuoteOptions, quote?.currency);
|
|
946
986
|
const lines: string[] = [];
|
|
947
987
|
|
|
948
988
|
lines.push(`${cliStyles.accent(quote?.symbol ?? symbol)} ${cliStyles.bold(name)}`);
|
|
@@ -953,9 +993,10 @@ export async function buildTickerReport({
|
|
|
953
993
|
const indexLevel = isIndexLevel(quote?.instrumentType?.trim() || financials.quoteMetadata?.instrumentType?.trim() || tickerFile?.metadata.assetCategory);
|
|
954
994
|
const summaryParts = [
|
|
955
995
|
listingVenueLabel(listingExchange, quote, financials, tickerFile) || undefined,
|
|
956
|
-
indexLevel ? INDEX_POINTS_LABEL : quoteCurrency ? `Currency ${quoteCurrency}` : undefined,
|
|
996
|
+
indexLevel ? INDEX_POINTS_LABEL : quoteCurrency ? `Currency ${currencyCodeLabel(quoteCurrency)}` : undefined,
|
|
957
997
|
quoteMarketStateLabel(quote) ?? undefined,
|
|
958
|
-
|
|
998
|
+
// A listing that no longer trades has no delay to speak of: its quote is its last session's close.
|
|
999
|
+
quote?.dataSource && !quote.delisting ? `Source ${quote.dataSource.toUpperCase()}` : undefined,
|
|
959
1000
|
].filter((part): part is string => !!part);
|
|
960
1001
|
if (summaryParts.length > 0) {
|
|
961
1002
|
lines.push(cliStyles.muted(summaryParts.join(METADATA_SEPARATOR)));
|
|
@@ -991,21 +1032,25 @@ export async function buildTickerReport({
|
|
|
991
1032
|
// Market cap and the multiples are measured at the regular session price the Quote section heads with.
|
|
992
1033
|
const priced = atRegularSessionPrice(financials);
|
|
993
1034
|
const capitalization = selectMarketCapitalization(priced.quote, priced.fundamentals);
|
|
994
|
-
|
|
1035
|
+
// Market cap and EV read in the currency they are reported in, as the revenue and income beside them do,
|
|
1036
|
+
// with the base currency's figure after them when it differs: 627.2B USD (878.15B CAD).
|
|
1037
|
+
const convertedCurrencies = new Set<string>();
|
|
1038
|
+
const inBase = async (value: number, currency: string): Promise<string> => {
|
|
1039
|
+
if (currency.trim().toUpperCase() === baseCurrency) return "";
|
|
1040
|
+
const converted = await toBase(value, currency);
|
|
1041
|
+
if (!Number.isFinite(converted)) return "";
|
|
1042
|
+
convertedCurrencies.add(currency.trim().toUpperCase());
|
|
1043
|
+
return ` (${formatCompact(converted)} ${baseCurrency})`;
|
|
1044
|
+
};
|
|
995
1045
|
const marketCapText = capitalization
|
|
996
|
-
?
|
|
997
|
-
? `${formatCompact(convertedMarketCap)} ${config.baseCurrency}`
|
|
998
|
-
: `${formatCompact(capitalization.value)} ${capitalization.currency}`
|
|
1046
|
+
? `${formatCompact(capitalization.value)} ${capitalization.currency}${await inBase(capitalization.value, capitalization.currency)}`
|
|
999
1047
|
: "—";
|
|
1000
1048
|
// Shown in the same currency as the market cap, so the two can be compared.
|
|
1001
1049
|
const enterpriseValue = reportedEnterpriseValue(priced.fundamentals);
|
|
1002
1050
|
const evCurrency = enterpriseValueCurrency(quote, fundamentals);
|
|
1003
1051
|
// A minor unit such as GBp stays as reported: the converter would read it as the major currency.
|
|
1004
|
-
const
|
|
1005
|
-
? await
|
|
1006
|
-
const enterpriseValueText = Number.isFinite(convertedEnterpriseValue)
|
|
1007
|
-
? `${formatCompact(convertedEnterpriseValue)} ${config.baseCurrency}`
|
|
1008
|
-
: formatReportedMoney(enterpriseValue, evCurrency);
|
|
1052
|
+
const enterpriseValueText = `${formatReportedMoney(enterpriseValue, evCurrency)}${enterpriseValue != null && evCurrency && /^[A-Z]{3}$/.test(evCurrency)
|
|
1053
|
+
? await inBase(enterpriseValue, evCurrency) : ""}`;
|
|
1009
1054
|
|
|
1010
1055
|
if (quote) {
|
|
1011
1056
|
// As the Overview reads it: the regular session, then the extended-hours print against its close.
|
|
@@ -1020,13 +1065,16 @@ export async function buildTickerReport({
|
|
|
1020
1065
|
const volume = reportedQuoteVolume(quote);
|
|
1021
1066
|
// A futures price is its latest trade; the exchange settlement is its own row, dated by its session.
|
|
1022
1067
|
appendMetricSection(lines, "Quote", [
|
|
1023
|
-
|
|
1068
|
+
// A price in cents says what it is per: 314.95¢/lb.
|
|
1069
|
+
["Last", colorBySign(`${formatMarketPriceWithCurrency(session.price, quote.currency, quoteOptions)}${minorUnitPricePer(futuresContract, quote.currency)}`, session.change)],
|
|
1024
1070
|
...futuresContract
|
|
1025
1071
|
? [["Settle", futuresSettleText(futuresContract, quote)] as [string, string]]
|
|
1026
1072
|
: [],
|
|
1027
|
-
|
|
1073
|
+
// The move says what it is measured from: "vs Fri 16 Oct close", a future's "vs Fri 16 Oct settle".
|
|
1074
|
+
["Change", quoteChangeText(quote, session, quoteOptions, futuresContract)],
|
|
1028
1075
|
["Open", quote.open != null ? formatMarketPriceWithCurrency(quote.open, quote.currency, quoteOptions) : "—"],
|
|
1029
|
-
|
|
1076
|
+
// A listing that no longer trades: its last session's range, never today's.
|
|
1077
|
+
[quote.delisting ? "Last Session" : "Day Range", quote.low != null || quote.high != null
|
|
1030
1078
|
? formatPriceRange(quote.low, quote.high, quote.currency, quoteOptions)
|
|
1031
1079
|
: "—"],
|
|
1032
1080
|
["52W Range", quote.low52w != null || quote.high52w != null
|
|
@@ -1047,10 +1095,11 @@ export async function buildTickerReport({
|
|
|
1047
1095
|
}
|
|
1048
1096
|
|
|
1049
1097
|
const metrics = fundamentalsMetrics(quote, priced.fundamentals, profile, { text: marketCapText }, priceReturns, fundamentalsCurrency(financials),
|
|
1050
|
-
{ text: enterpriseValueText }, distributions, bondFund);
|
|
1098
|
+
{ text: enterpriseValueText }, distributions, bondFund, codes);
|
|
1051
1099
|
const beforeFundamentals = lines.length;
|
|
1052
1100
|
appendMetricSection(lines, "Fundamentals", metricLines(metrics));
|
|
1053
1101
|
for (const note of fundamentalsNotes(priced, metrics, quote?.currency)) lines.push(cliStyles.muted(note));
|
|
1102
|
+
if (lines.length > beforeFundamentals) for (const note of conversionNotes(toBase, convertedCurrencies)) lines.push(cliStyles.muted(note));
|
|
1054
1103
|
// Where a fund's facts would go: under its fundamentals, or in their place.
|
|
1055
1104
|
const fund = fundFactsNote(quote?.symbol ?? symbol,
|
|
1056
1105
|
quote?.tradingExchangeName || listingExchange || quote?.listingExchangeName || quote?.exchangeName || tickerFile?.metadata.exchange, financials, tickerFile);
|
|
@@ -1068,13 +1117,13 @@ export async function buildTickerReport({
|
|
|
1068
1117
|
const latestAnnual = latestFinancialPeriod(financials.annualStatements, row => row.date);
|
|
1069
1118
|
if (latestAnnual) {
|
|
1070
1119
|
appendMetricSection(lines, `Latest Annual (${statementPeriodText(latestAnnual.date, statementFilings?.latestAnnual ?? null)})`,
|
|
1071
|
-
buildStatementMetrics(latestAnnual, statementCurrency(latestAnnual)));
|
|
1120
|
+
buildStatementMetrics(latestAnnual, statementCurrency(latestAnnual), codes));
|
|
1072
1121
|
}
|
|
1073
1122
|
|
|
1074
1123
|
const latestQuarter = latestFinancialPeriod(financials.quarterlyStatements, row => row.date);
|
|
1075
1124
|
if (latestQuarter) {
|
|
1076
1125
|
appendMetricSection(lines, `Latest Quarter (${statementPeriodText(latestQuarter.date, statementFilings?.latestQuarter ?? null)})`,
|
|
1077
|
-
buildStatementMetrics(latestQuarter, statementCurrency(latestQuarter)));
|
|
1126
|
+
buildStatementMetrics(latestQuarter, statementCurrency(latestQuarter), codes));
|
|
1078
1127
|
}
|
|
1079
1128
|
// The statements stop a quarter short of the results the company has released.
|
|
1080
1129
|
if (statementFilings?.resultsRelease && (latestAnnual || latestQuarter)) lines.push(cliStyles.muted(resultsReleaseText(statementFilings.resultsRelease)));
|
|
@@ -1158,6 +1207,10 @@ function buildTickerStructuredData({
|
|
|
1158
1207
|
name: quote.name,
|
|
1159
1208
|
// Last and Change as the text report reads them, then the extended print from that close.
|
|
1160
1209
|
...getQuoteSessionFields(quote),
|
|
1210
|
+
// What Change is measured from ("vs Fri 16 Oct close"), and the quote's previous close with its exchange-local date.
|
|
1211
|
+
changeBasis: changeBasisJson(quoteChangeBasis(quote, { terms: futuresContract })),
|
|
1212
|
+
previousClose: quote.previousClose ?? null,
|
|
1213
|
+
previousCloseDate: quotePreviousCloseDate(quote),
|
|
1161
1214
|
priceBasis: quote.priceBasis ?? null,
|
|
1162
1215
|
currency: quote.currency,
|
|
1163
1216
|
marketCap: priced.quote?.marketCap ?? null,
|